在一个单元测试中,我们经常编写多个@Test方法,来分级、分类对目标代码进行测试。
在测试的时候,我们经常遇到一个对象需要初始化,测试完可能还需要清理的情况。如果每个@Test方法都写一遍这样的重复代码,显然比较麻烦。
JUnit 提供了编写测试前准备、测试后清理的固定代码,我们称之为Fixture.
我们来看一个具体的Calculator的例子
public class Calculator {private long n = 0;public long add(long x) {n += x;return n;}public long sub(long x) {n -= x;return n;}}
这个类的功能很简单,但是测试的时候,我们要先初始化对象,我们不必在每个测试方法中都写上初始化代码,而是通过@BeforeEach来初始化,通过@AfterEach来清理资源。
public class CalculatorTest{
Calculator calculator;
@BeforeEach
public void setUp(){
this.calculator = new Calculator();
}
@AfterEach
public void tearDown(){
this.calculator = null;
}
@Test
void testSub(){
assertEquals(-2,this.calculator.sub(2));
assertEquals(-100,this.calculator.sub(100));
}
@Test
void testAdd(){
assertEquals(20000,this.calculator.add(20000));
assertEquals(200,this.calculator.add(200));
}
}
在CalculatorTest测试中,有两个标记为@BeforeEach和@AfterEach的方法,它们会在运行每个@Test方法前后自动运行。
上面的测试代码在JUnit中运行顺序如下
for(Method testMethod: findTestMethods(CalculatorTest.class)) {
var test = new CalculatorTest();
invokeBeforeEach(test);
invokeTestMethod(test,testMethod);
invokeAfterEach(test);
}
可见,@BeforeEach和@AfterEach会环绕在每个@Test方法前后。
还有一些资源初始化和清理可能更加繁琐,而且会耗费较长的时间,例如,初始化数据库。JUnit还提供了@BeforeAll和@AfterAll,它们在运行所有@Test前后运行,顺序如下
invokeBeforeAll(CalculatorTest.class);
for(Method testMethod: findTestMethods(CalculatorTest.class)) {
var test = new CalculatorTest();
invokeBeforeEach(test);
invokeTestMethod(test,testMethod);
invokeAfterEach(test);
}
invokeAfterAll(CalculatorTest.class);
因为@BeforeAll和@AfterAll在所有@Test方法运行前后仅运行一次,因此,它们只能初始化静态变量,
public class DatebaseTest{
static Database db;
@BeforeAll
public static void initDatabase(){
db = createDb(..);
}
@AfterAll
public static void dropDatabase(){
...
}
}
事实上,@BeforeAll和@AfterAll也只能标准在静态方法上。
因此,我们总结出编写Fixture的套路:
- 对于实例变量,在
@BeforeEach中初始化,在@AfterEach中清理,它们在各个@Test方法中互不影响,因为是不同的实例。 - 对于静态变量,在
@BeforeAll中初始化,在@AfterAll中清理,它们在各个@Test方法中均是唯一实例,会影响各个@Test方法。
大多数情况下, 使用@BeforeEach和@AfterEach就足够了,只有某些测试资源初始化耗费时间太长,以至于我们不得不尽量利用 时才会用 BeforeAll和AfterAll.
最后,注意到每次运行一个@Test方法前,JUnit首先创建一个XXTest实例,因此,每个@Test方法内部的成员变量都是独立的,不能也无法也成员变量的状态从一个@Test带到另一个@Test方法。
编写Fixture是指针对每个@Test方法,编写@BeforeEach方法用于初始化测试资源,编写@AfterEach用于清理测试资源;
必要时,可以编写@BeforeAll和@AfterAll,使用静态变量来初始化耗时的资源,并且在所有@Test方法的运行前后仅执行一次。
