共计 2247 个字符,预计需要花费 6 分钟才能阅读完成。
在一个单元测试中,我们经常编写多个 @Test
方法,来分组、分类对目标代码进行测试。
在测试的时候,我们经常遇到一个对象需要初始化,测试完可能还需要清理的情况。如果每个 @Test
方法都写一遍这样的重复代码,显然比较麻烦。
JUnit 提供了编写测试前准备、测试后清理的固定代码,我们称之为 Fixture。
我们来看一个具体的 Calculator
的例子:
public class Calculator {private long n = 0;
public long add(long x) {
n = n + x;
return n;
}
public long sub(long x) {
n = 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 testAdd() {assertEquals(100, this.calculator.add(100));
assertEquals(150, this.calculator.add(50));
assertEquals(130, this.calculator.add(-20));
}
@Test
void testSub() {assertEquals(-100, this.calculator.sub(100));
assertEquals(-150, this.calculator.sub(50));
assertEquals(-130, this.calculator.sub(-20));
}
}
在 CalculatorTest
测试中,有两个标记为 @BeforeEach
和@AfterEach
的方法,它们会在运行每个 @Test
方法前后自动运行。
上面的测试代码在 JUnit 中运行顺序如下:
for (Method testMethod : findTestMethods(CalculatorTest.class)) {var test = new CalculatorTest(); // 创建 Test 实例
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(); // 创建 Test 实例
invokeBeforeEach(test);
invokeTestMethod(test, testMethod);
invokeAfterEach(test);
}
invokeAfterAll(CalculatorTest.class);
因为 @BeforeAll
和@AfterAll
在所有 @Test
方法运行前后仅运行一次,因此,它们只能初始化静态变量,例如:
public class DatabaseTest {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 首先创建一个 XxxTest
实例,因此,每个 @Test
方法内部的成员变量都是独立的,不能也无法把成员变量的状态从一个 @Test
方法带到另一个 @Test
方法。
练习
使用 Fixture 编写单元测试。
下载练习
小结
编写 Fixture 是指针对每个 @Test
方法,编写 @BeforeEach
方法用于初始化测试资源,编写 @AfterEach
用于清理测试资源;
必要时,可以编写 @BeforeAll
和@AfterAll
,使用静态变量来初始化耗时的资源,并且在所有 @Test
方法的运行前后仅执行一次。