Appearance
Fixture
In unit testing, we often write multiple @Test methods to group and classify tests for the target code. During testing, we frequently encounter scenarios where an object needs initialization before the test and cleanup afterward. If we write this repetitive code in every @Test method, it becomes cumbersome.
JUnit provides a way to write fixed code for setup before tests and cleanup after tests, known as Fixture.
Let's look at a specific example using the Calculator class:
java
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;
}
}This class is simple, but when testing, we need to initialize the object. Instead of writing initialization code in every test method, we use @BeforeEach for setup and @AfterEach for cleanup:
java
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));
}
}In the CalculatorTest, there are two methods annotated with @BeforeEach and @AfterEach. These methods are automatically executed before and after each @Test method.
The sequence in which JUnit runs this test code is as follows:
java
for (Method testMethod : findTestMethods(CalculatorTest.class)) {
var test = new CalculatorTest(); // Create Test instance
invokeBeforeEach(test);
invokeTestMethod(test, testMethod);
invokeAfterEach(test);
}As you can see, @BeforeEach and @AfterEach "wrap" around each @Test method.
@BeforeAll and @AfterAll
Some resources, such as initializing a database, may be more complex and time-consuming. JUnit also provides @BeforeAll and @AfterAll, which run before and after all @Test methods. The execution order is:
java
invokeBeforeAll(CalculatorTest.class);
for (Method testMethod : findTestMethods(CalculatorTest.class)) {
var test = new CalculatorTest(); // Create Test instance
invokeBeforeEach(test);
invokeTestMethod(test, testMethod);
invokeAfterEach(test);
}
invokeAfterAll(CalculatorTest.class);Since @BeforeAll and @AfterAll only run once before and after all test methods, they can only initialize static variables, for example:
java
public class DatabaseTest {
static Database db;
@BeforeAll
public static void initDatabase() {
db = createDb(...);
}
@AfterAll
public static void dropDatabase() {
...
}
}In fact, @BeforeAll and @AfterAll must also be annotated on static methods.
Best Practices for Writing Fixtures
- For instance variables, initialize them in
@BeforeEachand clean them up in@AfterEach. Each test method has a separate instance, so they don't interfere with each other. - For static variables, initialize them in
@BeforeAlland clean them up in@AfterAll. These variables are shared among all@Testmethods.
Most of the time, @BeforeEach and @AfterEach are sufficient. You only need @BeforeAll and @AfterAll when resource initialization takes too long, and reuse is necessary.
Finally, note that JUnit creates a new instance of XxxTest before running each @Test method. This means that member variables inside each @Test method are independent, and the state of a member variable cannot be carried over from one test method to another.
Summary
Writing a Fixture involves creating a @BeforeEach method to initialize test resources and a @AfterEach method to clean them up for each @Test method.
If necessary, you can also write @BeforeAll and @AfterAll methods to initialize time-consuming resources with static variables, which are executed only once before and after all @Test methods.