如何在@TestFactory的每个动态测试中创建独立的Spring上下文?
问题:动态生成Spring测试时如何初始化独立上下文
我正尝试在项目中实现基于CSV的测试,以便在测试文件夹中新增带输入数据的CSV文件后,maven会自动运行新测试。
目前我使用@SpringJUnitConfig,并通过@Primary注解覆盖项目配置,现有代码如下:它会基于测试配置仅启动一个测试
package com.bch.score.test.framework; @SpringJUnitConfig({FrameworkSpringContext.class}) public class MySingleTestLauncher { @Autowired public MainApplicationService service; @Autowired public TestDbService dbPreparationService; @Autowired public IFrameworkDatabaseConfig dbSettings; @Autowired public TestOutputChecker outputChecker; @Test public void testSingleCase() { dbPreparationService.fillDatabase(dbSettings); service.launchApp(); outputChecker.checkResult(); } }
我在FrameworkSpringContext类中声明了同时扫描应用包和测试包的规则:
@Configuration @EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class}) @ComponentScan(value = {"com.bch.score.test.framework.config", "com.bch.score.cmd"}) public class FrameworkSpringContext { private final ISettingsProvider settings; @Autowired public FrameworkSpringContext(ISettingsProvider settings) { this.settings = settings; } @Bean public TestDbService service() { return new TestDbService(); } @Bean public TestOutputChecker outputAssertion() { return new TestOutputChecker(); } @Bean public IFrameworkDatabaseConfig dbSettings() { return new FrameworkDatabaseConfigImpl(settings.getUrl(), settings.getSchema(), settings.getLogin(), settings.getPassword()); } }
我还覆盖了应用内部使用的settings bean:
@PropertySource("classpath:score-framework.properties") @Configuration public class SettingsConfigurationFramework { @Primary @Bean(name = "testSettings") public ISettingsProvider settingsProvider(MessageSource messageSource, Environment environment) { return new SettingsProviderImpl(messageSource, environment); } }
这套方案运行正常,但每新增一个测试都需要复制MySingleTestLauncher和配置类。我希望使用@TestFactory按文件夹生成测试,预期的测试启动类写法如下:
@SpringJUnitConfig({FrameworkSpringContext.class}) public class MultipleTestLauncher { @Autowired public TestDbService dbPreparationService; @Autowired public Function<String, IFrameworkDatabaseConfig> dbSettingsFactory; @Autowired public TestOutputChecker outputChecker; @TestFactory public Collection<DynamicTest> testTests() throws IOException { List<Path> subFolders = Files.list(Paths.get("src", "test", "resources", "framework", "tests")) .filter(path -> path.getFileName().toString().startsWith("test_")) .collect(Collectors.toList()); List<DynamicTest> dynamicTests = new ArrayList<>(); for (Path testPath : subFolders) { String currentTestFolder = testPath.getFileName().toString(); dynamicTests.add(DynamicTest.dynamicTest(currentTestFolder, () -> { IFrameworkDatabaseConfig dbSettings = dbSettingsFactory.apply(currentTestFolder); dbPreparationService.fillDatabase(dbSettings); //TODO 以某种方式为每次应用启动创建新的Spring上下文... //TODO getTestContext().getBean(MainApplicationService.class).launchApp(); outputChecker.checkResult(); })); } } }
每个测试都要类似一次全新的应用启动,我需要测试之间完全隔离,避免互相影响输出结果。如果每个测试都有独立的Spring上下文,我就可以基于测试配置轻松mock ISettingsProvider,实现不同的断言逻辑。
我的问题是:为动态生成的测试初始化Spring上下文的最佳实践是什么?
我是否需要参考Spring的上下文缓存机制?在我看来,直接创建新的应用上下文更合理,可以隔离每次测试的应用启动过程,因此缓存可能不是最优选择。
解答
你要的完全隔离场景直接手动创建独立上下文即可,不需要使用Spring Test默认的上下文缓存机制,实现步骤如下:
- 先调整配置类,支持根据当前测试路径动态生成对应
ISettingsProvider
把原来的固定配置抽成可传入测试路径的构造形式,示例如下:
public class TestSpecificSettingsProvider extends SettingsProviderImpl { public TestSpecificSettingsProvider(String testFolder, MessageSource messageSource, Environment environment) { super(messageSource, environment); // 这里可以加载对应test文件夹下的自定义配置、CSV参数等 } }
- 修改动态测试的执行逻辑,手动初始化上下文,补全你的TODO部分:
dynamicTests.add(DynamicTest.dynamicTest(currentTestFolder, () -> { IFrameworkDatabaseConfig dbSettings = dbSettingsFactory.apply(currentTestFolder); dbPreparationService.fillDatabase(dbSettings); // 手动创建独立上下文 try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) { // 注册当前测试专属的Settings Bean context.registerBean(ISettingsProvider.class, () -> new TestSpecificSettingsProvider(currentTestFolder, context.getBean(MessageSource.class), context.getEnvironment()) ); // 注册公共配置类 context.register(FrameworkSpringContext.class, SettingsConfigurationFramework.class); // 刷新上下文完成初始化 context.refresh(); // 从当前独立上下文获取Bean执行业务逻辑 context.getBean(MainApplicationService.class).launchApp(); } outputChecker.checkResult(); }));
- 额外注意点:
- 用
try-with-resources包裹上下文初始化逻辑,测试执行完成后会自动关闭上下文,释放数据库连接、线程池等资源,不会出现泄漏 - 每个测试的上下文完全独立,Bean不会互相污染,完全满足你隔离测试的需求
- 如果后续测试数量上来启动变慢,再考虑给相同配置的测试加上下文缓存即可,当前阶段优先保证隔离性即可
内容的提问来源于stack exchange,提问作者Worm359
相关产品推荐
相关产品推荐

