JUnit测试中类为何重复初始化?final字段变化如何解决?
我在单元测试中发现final字段的值会发生变化,实际上整个测试类的实例会被重新初始化(类的哈希码也随之改变)。示例代码如下:
class SomeTest { private final String aRandomString = RandomStringUtils.randomAlphabetic(10); @Test void a() { } @Test void b() { } }
其中aRandomString在测试方法a和b中的值不同。我的POM文件中仅包含以下测试依赖:
<dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>${assertj-core.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
请问这是什么原因,有没有办法避免?(目前仅为理论疑问,暂无特定用例)
原因分析
你使用的Spring Boot Starter Test默认集成了JUnit 5,而JUnit的默认行为是为每个测试方法创建独立的测试类实例。这种设计是为了保证测试的隔离性,避免不同测试方法之间共享状态,防止测试结果互相干扰。
所以执行测试方法a()前,会初始化一个SomeTest实例,aRandomString被赋值为随机字符串;执行b()前,又会重新创建一个全新的SomeTest实例,aRandomString再次生成新的随机值,两个测试中的字段自然不同,实例哈希码也会变化。
避免方法
如果想让测试类实例仅初始化一次,所有测试方法共享同一个实例(以及其中的final字段),可以采用以下两种方式:
1. 使用JUnit 5的@TestInstance注解
在测试类上添加@TestInstance(TestInstance.Lifecycle.PER_CLASS),JUnit会为整个测试类创建唯一的实例,所有测试方法复用该实例:
import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; @TestInstance(Lifecycle.PER_CLASS) class SomeTest { private final String aRandomString = RandomStringUtils.randomAlphabetic(10); @Test void a() { } @Test void b() { } }
2. 将字段声明为static final
静态字段属于类而非实例,不管创建多少个测试类实例,静态final字段只会在类加载时初始化一次,所有测试方法共享同一个值:
class SomeTest { private static final String aRandomString = RandomStringUtils.randomAlphabetic(10); @Test void a() { } @Test void b() { } }
注意:使用静态字段或PER_CLASS生命周期时,要确保测试方法之间不会因共享状态产生干扰——如果测试方法会修改共享状态(哪怕是间接修改),可能导致测试结果不稳定。
内容的提问来源于stack exchange,提问作者Anna Klein

