测试环境设置过程的自动化,是测试中最具挑战性的部分,在单元测试、集成测试、系统测试中都是如此。测试执行所需要的固定环境称为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...");
}
}
打印:
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 无参,用例来初始化测试类级别的设置,所有测试方法之后仅执行一次 |
相应的执行流程,如下图: