使用Fixture

在一个单元测试中,我们经常编写多个@Test方法,来分组、分类对目标代码进行测试。

在测试的时候,我们经常遇到一个对象需要初始化,测试完可能还需要清理的情况。如果每个@Test方法都写一遍这样的重复代码,显然比较麻烦。

JUnit提供了编写测试前准备、测试后清理的固定代码,我们称之为Fixture。

我们来看一个具体的Calculator的例子:

  1. public class Calculator {
  2. private long n = 0;
  3. public long add(long x) {
  4. n = n + x;
  5. return n;
  6. }
  7. public long sub(long x) {
  8. n = n - x;
  9. return n;
  10. }
  11. }

这个类的功能很简单,但是测试的时候,我们要先初始化对象,我们不必在每个测试方法中都写上初始化代码,而是通过@BeforeEach来初始化,通过@AfterEach来清理资源:

  1. public class CalculatorTest {
  2. Calculator calculator;
  3. @BeforeEach
  4. public void setUp() {
  5. this.calculator = new Calculator();
  6. }
  7. @AfterEach
  8. public void tearDown() {
  9. this.calculator = null;
  10. }
  11. @Test
  12. void testAdd() {
  13. assertEquals(100, this.calculator.add(100));
  14. assertEquals(150, this.calculator.add(50));
  15. assertEquals(130, this.calculator.add(-20));
  16. }
  17. @Test
  18. void testSub() {
  19. assertEquals(-100, this.calculator.sub(100));
  20. assertEquals(-150, this.calculator.sub(50));
  21. assertEquals(-130, this.calculator.sub(-20));
  22. }
  23. }

CalculatorTest测试中,有两个标记为@BeforeEach@AfterEach的方法,它们会在运行每个@Test方法前后自动运行。

上面的测试代码在JUnit中运行顺序如下:

  1. for (Method testMethod : findTestMethods(CalculatorTest.class)) {
  2. var test = new CalculatorTest(); // 创建Test实例
  3. invokeBeforeEach(test);
  4. invokeTestMethod(test, testMethod);
  5. invokeAfterEach(test);
  6. }

可见,@BeforeEach@AfterEach会“环绕”在每个@Test方法前后。

还有一些资源初始化和清理可能更加繁琐,而且会耗费较长的时间,例如初始化数据库。JUnit还提供了@BeforeAll@AfterAll,它们在运行所有@Test前后运行,顺序如下:

  1. invokeBeforeAll(CalculatorTest.class);
  2. for (Method testMethod : findTestMethods(CalculatorTest.class)) {
  3. var test = new CalculatorTest(); // 创建Test实例
  4. invokeBeforeEach(test);
  5. invokeTestMethod(test, testMethod);
  6. invokeAfterEach(test);
  7. }
  8. invokeAfterAll(CalculatorTest.class);

因为@BeforeAll@AfterAll在所有@Test方法运行前后仅运行一次,因此,它们只能初始化静态变量,例如:

  1. public class DatabaseTest {
  2. static Database db;
  3. @BeforeAll
  4. public static void initDatabase() {
  5. db = createDb(...);
  6. }
  7. @AfterAll
  8. public static void dropDatabase() {
  9. ...
  10. }
  11. }

事实上,@BeforeAll@AfterAll也只能标注在静态方法上。

因此,我们总结出编写Fixture的套路如下:

  • 对于实例变量,在@BeforeEach中初始化,在@AfterEach中清理,它们在各个@Test方法中互不影响,因为是不同的实例;

  • 对于静态变量,在@BeforeAll中初始化,在@AfterAll中清理,它们在各个@Test方法中均是唯一实例,会影响各个@Test方法。

大多数情况下,使用@BeforeEach@AfterEach就足够了。只有某些测试资源初始化耗费时间太长,以至于我们不得不尽量“复用”时才会用到@BeforeAll@AfterAll

最后,注意到每次运行一个@Test方法前,JUnit首先创建一个XxxTest实例,因此,每个@Test方法内部的成员变量都是独立的,不能也无法把成员变量的状态从一个@Test方法带到另一个@Test方法。

练习

下载练习:使用Fixture (推荐使用IDE练习插件快速下载)

小结

编写Fixture是指针对每个@Test方法,编写@BeforeEach方法用于初始化测试资源,编写@AfterEach用于清理测试资源;

必要时,可以编写@BeforeAll@AfterAll,使用静态变量来初始化耗时的资源,并且在所有@Test方法的运行前后仅执行一次。

读后有收获可以支付宝请作者喝咖啡,读后有疑问请加微信群讨论

使用Fixture - 图1使用Fixture - 图2