如何通过JUnit Platform Launcher API向@TestFactory传递复杂对象
解决方案:JUnit Platform Launcher传递非序列化复杂对象到动态测试
问题背景
当前通过JUnit Platform Launcher API运行动态测试时,仅能通过configurationParameter传递序列化字符串,但需要复用FeignClient、JdbcClient这类无法序列化的复杂对象,避免重复创建客户端资源。
现有核心代码:
- Launcher初始化代码:
Launcher launcher = LauncherFactory.create(); LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() .configurationParameter(META_TEST_INFORMATION_KEY, serializedString) .selectors(selectPackage("my.package")) .build();
- 测试类:
@ExtendWith(CustomParameterResolver.class) public class JUnitTestRunner { private static final Logger log = LogManager.getLogger(JUnitTestRunner.class); @TestFactory Collection<DynamicTest> dynamicTestCollection(String data) throws TestFrameworkException { TestMetaData metaData = deserializeObject(data); List<DynamicTest> testList = new ArrayList<>(); for (int i = 0; i < metaData.getGroupedOrderedTestCases().size(); i++) { TestCaseMetaInfo testCaseMetaInfo = metaData.getMapOfNumberedTestCaseMetaInfo() .get(metaData.getGroupedOrderedTestCases().get(i)); testList.add(DynamicTest.dynamicTest( testCaseMetaInfo.getName(), () -> log.info("Running TEST Number " + testCaseMetaInfo.getCaseNumber()) )); } return testList; } }
- 自定义ParameterResolver:
public class CustomParameterResolver implements ParameterResolver { private static final Logger log = LogManager.getLogger(CustomParameterResolver.class); @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return parameterContext.getParameter().getType() == String.class; } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return extensionContext.getConfigurationParameter(META_TEST_INFORMATION_KEY).orElse(""); } }
解决方案步骤
1. 实现全局客户端持有扩展
创建JUnit Extension,负责存储全局复用的客户端对象,同时支持测试完成后的资源清理:
public class GlobalClientHolderExtension implements BeforeAllCallback, ExtensionContext.Store.CloseableResource { private static FeignClient feignClient; private static JdbcClient jdbcClient; // 静态方法:在Launcher初始化前注入客户端实例 public static void setClients(FeignClient feign, JdbcClient jdbc) { feignClient = feign; jdbcClient = jdbc; } @Override public void beforeAll(ExtensionContext context) throws Exception { // 将客户端存入全局命名空间的Store,确保所有测试类可访问 ExtensionContext.Store globalStore = context.getRoot().getStore(ExtensionContext.Namespace.GLOBAL); globalStore.put("FEIGN_CLIENT", feignClient); globalStore.put("JDBC_CLIENT", jdbcClient); } @Override public void close() throws Throwable { // 可选:测试结束后关闭客户端资源 if (feignClient != null) { // 执行FeignClient关闭逻辑 } if (jdbcClient != null) { // 执行JdbcClient关闭逻辑 } } }
2. 修改Launcher初始化逻辑
先初始化复用的客户端,注入到全局扩展后,再注册扩展到测试执行流程:
// 1. 初始化客户端(仅执行一次) FeignClient feignClient = buildFeignClient(); // 替换为你的客户端创建逻辑 JdbcClient jdbcClient = buildJdbcClient(); // 2. 将客户端注入全局扩展 GlobalClientHolderExtension.setClients(feignClient, jdbcClient); // 3. 构建测试请求并注册扩展 Launcher launcher = LauncherFactory.create(); LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() .configurationParameter(META_TEST_INFORMATION_KEY, serializedString) .selectors(selectPackage("my.package")) .extensions(GlobalClientHolderExtension.class) // 注册全局客户端扩展 .build(); // 4. 执行测试并输出结果 SummaryGeneratingListener listener = new SummaryGeneratingListener(); launcher.execute(request, listener); listener.getSummary().printTo(new PrintWriter(System.out));
3. 扩展ParameterResolver支持复杂对象解析
修改自定义Resolver,添加对FeignClient、JdbcClient的解析逻辑:
public class CustomParameterResolver implements ParameterResolver { private static final Logger log = LogManager.getLogger(CustomParameterResolver.class); @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { Class<?> paramType = parameterContext.getParameter().getType(); // 支持String、FeignClient、JdbcClient类型参数 return paramType == String.class || paramType == FeignClient.class || paramType == JdbcClient.class; } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { Class<?> paramType = parameterContext.getParameter().getType(); ExtensionContext.Store globalStore = extensionContext.getRoot().getStore(ExtensionContext.Namespace.GLOBAL); if (paramType == String.class) { return extensionContext.getConfigurationParameter(META_TEST_INFORMATION_KEY).orElse(""); } else if (paramType == FeignClient.class) { FeignClient client = globalStore.get("FEIGN_CLIENT", FeignClient.class); if (client == null) throw new ParameterResolutionException("FeignClient未初始化"); return client; } else if (paramType == JdbcClient.class) { JdbcClient client = globalStore.get("JDBC_CLIENT", JdbcClient.class); if (client == null) throw new ParameterResolutionException("JdbcClient未初始化"); return client; } throw new ParameterResolutionException("不支持的参数类型:" + paramType); } }
4. 测试类中直接注入客户端
修改测试类,添加客户端参数并注册全局扩展:
@ExtendWith({CustomParameterResolver.class, GlobalClientHolderExtension.class}) public class JUnitTestRunner { private static final Logger log = LogManager.getLogger(JUnitTestRunner.class); @TestFactory Collection<DynamicTest> dynamicTestCollection(String data, FeignClient feignClient, JdbcClient jdbcClient) throws TestFrameworkException { TestMetaData metaData = deserializeObject(data); List<DynamicTest> testList = new ArrayList<>(); for (int i = 0; i < metaData.getGroupedOrderedTestCases().size(); i++) { TestCaseMetaInfo testCaseMetaInfo = metaData.getMapOfNumberedTestCaseMetaInfo() .get(metaData.getGroupedOrderedTestCases().get(i)); // 捕获当前测试用例,避免Lambda变量捕获问题 TestCaseMetaInfo currentTestCase = testCaseMetaInfo; testList.add(DynamicTest.dynamicTest( currentTestCase.getName(), () -> { log.info("Running TEST Number " + currentTestCase.getCaseNumber()); // 直接复用客户端执行测试逻辑 feignClient.invokeApi(currentTestCase.getApiDetails()); jdbcClient.executeQuery(currentTestCase.getSqlQuery()); } )); } return testList; } }
方案优势
- 资源复用:客户端仅初始化一次,全局共享,避免重复创建带来的性能开销
- 安全合规:通过JUnit官方ExtensionContext Store机制传递对象,符合JUnit扩展规范
- 无序列化成本:直接传递对象实例,规避复杂对象序列化/反序列化的兼容性问题
内容的提问来源于stack exchange,提问作者dnWick
相关产品推荐
相关产品推荐

