測試環境設置過程的自動化,是測試中最具挑戰性的部分,在單元測試、集成測試、系統測試中都是如此。測試執行所需要的固定環境稱為Test Fixture。
Test Fixture(測試固件?)是指一個測試運行所需的固定環境,準確的定義:
The test fixture is everything we need to have in place to exercise the SUT
通俗點講就是測試上下文或者說是測試環境,也是就是測試運行之前所需的穩定的、公共的可重復的運行環境,這個“環境”不僅可以是數據,也可以指對被測軟件的準備,例如實例化被測方法所依賴的類、加載數據庫等等。
不同級別的測試所需的Test Fixture是不同的:
Test Fixture | Test Fixture例子 |
---|---|
單元測試 | ?創建新對象?準備輸入數據 |
集成測試 | ?將數據庫重置為初始狀態?復制在測試過程中使用的文件 |
系統測試 | ?安裝提供運行時環境的虛擬?安裝(或清理某些初始狀態)的Web服務器和數據庫由應用程序使用應用 |
因此,Fixtures 是測試中非常重要的一部分。他們的主要目的是建立一個固定/已知的環境狀態以確保 測試可重復并且按照預期方式運行。Junit提供了一些方法來設置fixture,可以用來設置測試方法所需的環境數據等,允許你精確的定義你的Fixtures。大致上分為三類:
- Test Fixtures
- 規則(Rules&RulesClass)
- Theories
Test Fixtures
JUnit提供可以在每個測試運行前后都運行fixture,或者在所有測試方法前后只運行一次fixture的注解。這允許我們在測試前后設置和清理數據或環境等測試條件。JUnit有兩個類級別(@BeforeClass和@AfterClass),兩個方法級別(@Afer和@Before)總共四個fixture的注解。
- @Before - 在每個@Test方法之前運行
- @After - 在每個@Test方法之后運行
- @BeforeClass - 在所有的@Test方法之前運行一次
- @AfterClass - 在所有的@Test方之后運行一次
Before&After
在編寫測試時,通常會發現幾個測試方法需要在運行之前需要類似的或者同樣的對象。這個時候就可以使用@Before。@Before用來標注一個public void方法,該方法會再在@Test方法之前運行。超類的@Before方法將在當前類之前運行。
public class BeforeTest {
/**
* 該方法在test方法執行之前執行
*/
@Before
public void runBeforeEveryTestMethod(){
System.out.println("@Before each method");
}
@Test
public void testOne() {
System.out.println("testOne testing");
}
@Test
public void testTwo() {
System.out.println("testTwo testing");
}
}
運行上述測試將會打印:
@Before each method
testTwo testing
@Before each method
testOne testing
如果創建一個類繼承上述的類,那么子類中的@Before方法會在測試方法之前父類的@Before執行之后執行:
public class BeforeTestExtend extends BeforeTest {
@Before
public void runBeforeEveryTestMethodAfterSuperClass(){
System.out.println("@Before each method subClass");
}
}
運行:
@Before each method
@Before each method subClass
testTwo testing
@Before each method
@Before each method subClass
testOne testing
與@Before相對的是@After注解。@After標注的方法將在每個@Test方法之后運行。如果我們在Before方法中使用了外部資源(如打開文件,數據庫鏈接。。),則需要在測試運行后釋放它們,這種情況下@After就可使用了。
@After用來標注一個public void方法,使得該方法在@Test方法之后運行。需要注意的是,即使@Before或@Test方法拋出異常,@After方法還是會保證運行。
但是如果是構造函數拋出異常,@After不會運行。
超類中聲明的@After方法將在當前類之后運行。
public class AfterTest {
File output;
//Test Fixtures
@Before
public void setUp() throws Exception {
output = new File("output");
System.out.println("Before Start...");
}
@After
public void tearDown() throws Exception {
output.delete();
System.out.println("After Starting...");
}
//Test Methods
@Test
public void testOne() {
System.out.println("TestOne Runing!");
}
@Test
public void testTwo() {
System.out.println("TestTwo Runing!");
}
}
執行測試,會打印:
Before Start...
TestOne Runing!
After Starting...
Before Start...
TestTwo Runing!
After Starting...
如果@Before方法里拋出了異常,@Test方法會跳過,但是@After還是會執行:
public class AfterTest {
File output;
//Test Fixtures
@Before
public void setUp() throws Exception {
output = new File("output");
System.out.println("Before Start...");
throw new Exception();
}
@After
public void tearDown() throws Exception {
output.delete();
System.out.println("After Starting...");
}
// Test Methods
@Test
public void testOne() {
System.out.println("TestOne Runing!");
}
@Test
public void testTwo() {
System.out.println("TestTwo Runing!");
}
}
運行測試會失敗,但是@After會運行,將打印:
Before Start...
After Starting...
Before Start...
After Starting...
BeforeClass&AfterClass
@Before @After 是每個測試方法私有的環境設置。但是如果有多個測試共享同樣的測試環境,特別是這些環境設置是比較昂貴的計算時,就沒必要每個測試都設置,幾個測試可以共享同樣的測試Fixture,雖然這可能會影響測試的獨立性,但有時候這是一個必要的優化。
在測試類以什么樣的方式組織時就需要考慮到這一點。是一個對象對應一個測試類?還是一個業務場景對應一個測試類呢?一般來說比較好的做法是一個業務場景對應一個測試類,因為在同樣的業務場景下,整個測試類共享測試Fixture的情況比較多,這樣就可以使用后共享測試Fixture減少代碼。
在Junit4中是使用@BeforeClass、@AfterClass的方法來實現共享Fixture。@BeforeClass、@AfterClass用來標注一個public static void no-arg方法,使得它在類中的所有測試方法運行之前/之后運行一次,也就是說如果你的測試類有十個測試,@Before/@After代碼將被執行十次,但@BeforeClass/@AfterClass將只執行一次。超類的@BeforeClass方法將在當前類之前運行。一般來說,當多個測試需要共享相同的計算昂貴的設置代碼時,就可以使用@BeforeClass。 比如建立數據庫連接這一類。 當然可以不用@BeforeClass,可以將代碼直接放到@Before,但是測試運行可能需要更花更多的時間。需要注意的是,標記@BeforeClass的代碼是靜態初始化運行的,因此它將在測試類的實例創建之前運行。由于是在測試類的實例創建之前,因此它會再@Before 方法之前運行,對應的@AfterClass會在@After之后運行。
public class ShareFixtureTest {
// Test Fixture
@BeforeClass
public static void breforeTestOnlyOnce() throws Exception {
System.out.println("Run before all test only once...");
}
@AfterClass
public static void afterTestOnlyOnce() throws Exception {
System.out.println("Run after all test only once...");
}
@Before
public void beforePerTest(){
System.out.println("Run before per test ...");
}
@After
public void afterPerTest(){
System.out.println("Run after per test ...");
}
//Test Method
@Test
public void testOne() {
System.out.println("testOne Start...");
}
@Test
public void testTwo() {
System.out.println("testTwo Start...");
}
}
打?。?/p>
Run before all test only once...
Run before per test ...
testOne Start...
Run after per test ...
Run before per test ...
testTwo Start...
Run after per test ...
Run after all test only once...
如果在測試的構造函數統計一下實例個數,可以發現,@BeforeClass在測試類的實例創建之前就開始運行了,以下代碼僅做測試用:
public class ShareFixtureTest {
private static int quantity = 0;
public ShareFixtureTest() {
quantity++;
}
//Test Fixtures
@BeforeClass
public static void breforeTestOnlyOnce() throws Exception {
System.out.println("Run before all test only once..." + quantity);
}
@AfterClass
public static void afterTestOnlyOnce() throws Exception {
System.out.println("Run after all test only once...");
}
//Test Methods
@Test
public void testOne() {
System.out.println("testOne Start..." + quantity);
}
@Test
public void testTwo() {
System.out.println("testTwo Start..." + quantity);
}
}
前面提過每個測試方法都會在單獨的測試類的實例里面運行,實例就是會創建兩個實例,但是@BeforeClass在測試實例創建之前就執行,所以打印0。
Run before all test only once...0
testOne Start...1
testTwo Start...2
Run after all test only once...
在JUnit 5中,標簽@BeforeEach和@BeforeAll是JUnit 4中的@Before和@BeforeClass的等價物。它們的名稱更多地表示了它們何時運行:“在每次測試之前”和“在所有測試之前”。
總結
注解 | 描述 |
---|---|
@Before | 非靜態方法 void,(可以多次發生)來初始化測試方法級別的設置,每個@Test方法運行之前運行一次 |
@After | 非靜態方法 void,(可以多次發生)來清理測試方法級別的設置,每個@Test方法運行之后運行一次 |
@BeforeClass | 靜態方法 void 無參,用例來初始化測試類級別的設置,所有測試方法之前僅執行一次 |
@AfterClass | 靜態方法 void 無參,用例來初始化測試類級別的設置,所有測試方法之后僅執行一次 |
相應的執行流程,如下圖: