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
@BeforeEach
and 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
@BeforeAll
and clean them up in@AfterAll
. These variables are shared among all@Test
methods.
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.