JUnit 5 TestInstanceFactory 功能与使用详解
JUnit 5 TestInstanceFactory
功能与使用详解
TestInstanceFactory
是 JUnit 5 的扩展接口,允许开发者自定义测试类实例的创建逻辑。默认情况下,JUnit 会为每个测试方法创建一个新的测试类实例(PER_METHOD
模式),但通过 TestInstanceFactory
,可以实现以下目标:
- 单例测试实例:所有测试方法共享同一个实例(类似
PER_CLASS
模式)。 - 依赖注入:将测试实例的创建委托给依赖注入容器(如 Spring、Guice)。
- 资源复用:在测试实例中共享昂贵资源(如数据库连接池)。
一、TestInstanceFactory
核心方法
接口定义:
public interface TestInstanceFactory {
Object createTestInstance(TestInstanceFactoryContext context, ExtensionContext extensionContext) throws TestInstantiationException;
}
TestInstanceFactoryContext
:包含测试类的Class
对象和构造函数参数。ExtensionContext
:提供测试执行的上下文信息。
二、使用场景
- 单例测试实例
所有测试方法共享同一个实例(适合需要共享状态的测试)。 - 依赖注入容器集成
通过外部容器(如 Spring)创建测试实例,实现自动装配。 - 延迟初始化
按需创建测试实例,优化资源使用。
三、具体示例
示例 1:实现单例测试实例
目标:所有测试方法共享同一个测试类实例。
步骤 1:实现 TestInstanceFactory
import org.junit.jupiter.api.extension.*;
public class SingletonTestInstanceFactory implements TestInstanceFactory {
private Object instance;
@Override
public Object createTestInstance(TestInstanceFactoryContext context, ExtensionContext extensionContext) {
if (instance == null) {
try {
instance = context.getTestClass().getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new TestInstantiationException("创建单例实例失败", e);
}
}
return instance;
}
}
步骤 2:在测试类中注册扩展
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(SingletonTestInstanceFactory.class)
public class SingletonTest {
private int counter = 0;
@Test
void test1() {
counter++;
System.out.println("test1 counter: " + counter); // 输出 1
}
@Test
void test2() {
counter++;
System.out.println("test2 counter: " + counter); // 输出 2
}
}
输出:
test1 counter: 1
test2 counter: 2
解释:
- 两个测试方法共享同一个
SingletonTest
实例,counter
状态被保留。 - 通过
SingletonTestInstanceFactory
确保只创建一个实例。
示例 2:集成依赖注入容器(伪代码)
目标:通过 Guice 容器创建测试实例并注入依赖。
步骤 1:实现 TestInstanceFactory
import com.google.inject.*;
import org.junit.jupiter.api.extension.*;
public class GuiceTestInstanceFactory implements TestInstanceFactory {
private static final Injector injector = Guice.createInjector(new AppModule());
@Override
public Object createTestInstance(TestInstanceFactoryContext context, ExtensionContext extensionContext) {
return injector.getInstance(context.getTestClass());
}
}
步骤 2:定义 Guice 模块
public class AppModule extends AbstractModule {
@Override
protected void configure() {
bind(UserService.class).to(UserServiceImpl.class);
}
}
步骤 3:在测试类中使用
@ExtendWith(GuiceTestInstanceFactory.class)
public class UserServiceTest {
@Inject
private UserService userService;
@Test
void testUserService() {
Assertions.assertNotNull(userService);
}
}
解释:
GuiceTestInstanceFactory
通过 Guice 容器创建测试实例。UserService
依赖被自动注入到测试类中。
四、与 @TestInstance
注解的区别
特性 | TestInstanceFactory | @TestInstance(Lifecycle.PER_CLASS) |
---|---|---|
控制粒度 | 完全自定义实例创建逻辑 | 仅支持 PER_CLASS 或 PER_METHOD 两种模式 |
灵活性 | 高(可与依赖注入框架集成) | 低(仅内置模式) |
适用场景 | 复杂实例管理(如单例、容器集成) | 简单的共享实例需求 |
五、注意事项
- 线程安全
在并行测试中,单例实例可能导致状态污染,需确保线程安全。 - 生命周期管理
若测试实例持有资源(如数据库连接),需手动管理清理逻辑。 - 构造函数参数
JUnit 默认通过无参构造函数创建实例,若需传递参数,需在工厂中处理。
六、总结
TestInstanceFactory
的核心价值:
- 高度可控:完全掌控测试实例的创建过程。
- 框架集成:无缝对接依赖注入容器或自定义初始化逻辑。
- 资源优化:通过共享实例减少资源消耗。
推荐使用场景:
- 需要测试方法共享状态的集成测试。
- 与 Spring、Guice 等依赖注入框架深度集成。
- 实现复杂的测试实例初始化逻辑(如缓存预热)。
通过合理使用 TestInstanceFactory
,可以显著提升测试的灵活性和可维护性!