You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 09:35:20