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

如何在JUnit5中以编程方式注册动态参数解析扩展?

问题

测试场景需要一个仅在运行时才能确定的MyParameter参数,已实现对应的ParameterResolver扩展MyParameterExtension。当前通过自定义代码启动JUnit 5测试,参数值可在启动时确定(比如从CLI获取),需要在测试执行前注册该扩展,同时要支持多线程并行运行不同参数的测试(例如分别传入“地球重力”“火星重力”参数)。

已排除以下方案:自动注册扩展、@RegisterExtension注解、ThreadLocal、静态字段,询问合理的实现方式。

相关代码示例:

原测试类

@ExtendWith(MyParameterExtension.class)
public class Test {

  protected final MyParameter p;

  public Test(MyParameter p) {}

  @Test
  public void test() { assertSuccess(TestedCode.doComplexThing(p)); }
}

原扩展实现

class MyParameterExtension implements ParameterResolver {

    private final MyParameter myParameter;
 
    public MyParameterExtension(MyParameter p) {
       myParameter = p;
    }

    @Override
    public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
        return (parameterContext.getParameter().getType() == MyParameter.class);
    }

    @Override
    public MyParameter resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
        return myParameter;
    }
}

原启动代码

void launchSuite(List<DiscoverySelector> selectors, Object something) {
    // The input to this are all the necessary selectors.
    LauncherDiscoveryRequest ldr = LauncherDiscoveryRequestBuilder.request()
       .selectors(selectors).build();
    Launcher launcher = LauncherFactory.create();
    TestPlan plan = launcher.discover(ldr);
    MyParameter myParameter = new MyParameter(something);
    MyParameterExtension ext = new MyParameterExtension(myParameter);
    // $TODO: how do I register my extension with the test run
    // before starting it?
    launcher.execute(plan);
}

解决方案

核心思路是为每个参数值创建独立的测试执行上下文,动态注册携带对应参数的扩展实例,并通过线程池实现并行执行,完全规避被排除的方案,同时保证参数隔离。

步骤说明

  1. 移除静态扩展绑定:删除测试类上的@ExtendWith(MyParameterExtension.class)注解,改为通过启动代码动态注册扩展。
  2. 参数隔离的扩展注册:为每个待测试的参数值,创建专属的MyParameterExtension实例,并将其绑定到对应的LauncherDiscoveryRequest中。
  3. 并行执行测试:使用线程池启动多个独立的测试任务,每个任务对应一个参数值,确保多线程下参数互不干扰。

修改后的代码示例

测试类(移除静态注解)

public class GravityTest {

    protected final MyParameter p;

    public GravityTest(MyParameter p) {}

    @Test
    public void testComplexCalculation() {
        assertSuccess(TestedCode.doComplexThing(p));
    }
}

扩展实现(无需修改)

class MyParameterExtension implements ParameterResolver {

    private final MyParameter myParameter;

    public MyParameterExtension(MyParameter p) {
        myParameter = p;
    }

    @Override
    public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
        return parameterContext.getParameter().getType() == MyParameter.class;
    }

    @Override
    public MyParameter resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
        return myParameter;
    }
}

并行启动代码

import org.junit.platform.launcher.Launcher;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
import org.junit.platform.launcher.LauncherFactory;
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
import org.junit.platform.launcher.listeners.SummaryGeneratingListener;
import org.junit.platform.launcher.listeners.TestExecutionSummary;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

void launchParallelTests(List<Object> parameterValues, List<DiscoverySelector> selectors) {
    // 根据参数数量初始化线程池,也可使用固定大小线程池
    ExecutorService executor = Executors.newFixedThreadPool(parameterValues.size());

    for (Object paramValue : parameterValues) {
        executor.submit(() -> {
            // 为当前参数创建专属扩展实例
            MyParameter myParameter = new MyParameter(paramValue);
            MyParameterExtension extension = new MyParameterExtension(myParameter);

            // 构建包含当前扩展的测试发现请求
            LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
                    .selectors(selectors)
                    .extensions(extension) // 动态注册带参数的扩展
                    .build();

            // 创建独立的Launcher执行测试
            Launcher launcher = LauncherFactory.create();
            SummaryGeneratingListener resultListener = new SummaryGeneratingListener();
            launcher.registerTestExecutionListeners(resultListener);

            // 执行当前参数的测试套件
            launcher.execute(request);

            // 打印测试结果(可根据需求调整)
            TestExecutionSummary summary = resultListener.getSummary();
            System.out.printf("参数[%s]测试完成:成功%d个,失败%d个%n",
                    paramValue, summary.getTestsSucceededCount(), summary.getTestsFailedCount());
        });
    }

    // 关闭线程池并等待所有任务结束
    executor.shutdown();
    try {
        if (!executor.awaitTermination(1, TimeUnit.HOURS)) {
            executor.shutdownNow();
        }
    } catch (InterruptedException e) {
        executor.shutdownNow();
        Thread.currentThread().interrupt();
    }
}

方案优势

  • 参数完全隔离:每个参数对应独立的扩展实例和测试执行上下文,多线程并行时不会出现参数串扰。
  • 动态扩展注册:完全通过启动代码控制扩展注册,不依赖静态注解或自动注册机制。
  • 并行效率高:线程池管理测试任务,充分利用多核资源执行不同参数的测试。

内容的提问来源于stack exchange,提问作者Pawel Veselov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 14:45:34