在一个单元测试中,我们经常编写多个@Test方法,来分级、分类对目标代码进行测试。
    在测试的时候,我们经常遇到一个对象需要初始化,测试完可能还需要清理的情况。如果每个@Test方法都写一遍这样的重复代码,显然比较麻烦。
    JUnit 提供了编写测试前准备、测试后清理的固定代码,我们称之为Fixture.

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

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

    这个类的功能很简单,但是测试的时候,我们要先初始化对象,我们不必在每个测试方法中都写上初始化代码,而是通过@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的套路:

    1. 对于实例变量,在@BeforeEach中初始化,在@AfterEach中清理,它们在各个@Test方法中互不影响,因为是不同的实例。
    2. 对于静态变量,在@BeforeAll中初始化,在@AfterAll中清理,它们在各个@Test方法中均是唯一实例,会影响各个@Test方法。

    大多数情况下, 使用@BeforeEach@AfterEach就足够了,只有某些测试资源初始化耗费时间太长,以至于我们不得不尽量利用 时才会用 BeforeAll和AfterAll.
    最后,注意到每次运行一个@Test方法前,JUnit首先创建一个XXTest实例,因此,每个@Test方法内部的成员变量都是独立的,不能也无法也成员变量的状态从一个@Test带到另一个@Test方法。

    编写Fixture是指针对每个@Test方法,编写@BeforeEach方法用于初始化测试资源,编写@AfterEach用于清理测试资源;
    必要时,可以编写@BeforeAll和@AfterAll,使用静态变量来初始化耗时的资源,并且在所有@Test方法的运行前后仅执行一次。