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

如何在JUnit 5/Jupiter中重复执行含参数化测试的测试类?

在JUnit 5中实现重复执行测试类并共享实例的方案

可以实现你的需求,核心思路是利用JUnit Jupiter的TestTemplate扩展机制,自定义测试模板上下文提供者,控制每次重复周期的实例初始化逻辑,确保同一轮重复中的所有参数化测试共享同一个Foo实例,且每轮重复都会重新初始化Foo。

步骤1:自定义重复执行注解

定义一个注解用来指定重复次数,简化扩展的使用:

import org.junit.jupiter.api.extension.ExtendWith;
import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(RepeatWithSharedFooProvider.class)
public @interface RepeatWithSharedFoo {
    // 默认重复5次,可根据需求自定义
    int value() default 5;
}

步骤2:实现测试模板上下文提供者

这个类负责生成重复执行的上下文,每轮重复前初始化Foo实例:

import org.junit.jupiter.api.extension.*;
import java.util.stream.Stream;

public class RepeatWithSharedFooProvider implements TestTemplateInvocationContextProvider {

    @Override
    public boolean supportsTestTemplate(ExtensionContext context) {
        return context.getTestClass()
                .map(clazz -> clazz.isAnnotationPresent(RepeatWithSharedFoo.class))
                .orElse(false);
    }

    @Override
    public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) {
        // 读取注解中指定的重复次数
        int repeatCount = context.getTestClass()
                .map(clazz -> clazz.getAnnotation(RepeatWithSharedFoo.class).value())
                .orElse(5);

        return Stream.generate(() -> new TestTemplateInvocationContext() {
            @Override
            public String getDisplayName(int invocationIndex) {
                return "重复周期 " + (invocationIndex + 1);
            }

            @Override
            public void prepareTestInstance(ExtensionContext context) throws Exception {
                // 每轮重复前初始化Foo实例
                Test testInstance = context.getTestInstance().get();
                testInstance.foo = generateRandomFoo();
            }
        }).limit(repeatCount);
    }

    // 替换成你实际的随机Foo生成逻辑
    private Foo generateRandomFoo() {
        return new Foo();
    }

    // 示例Foo类,替换成你的实际类
    static class Foo {
        public java.util.List<Integer> bar(int i) {
            return java.util.Collections.nCopies(i, 1);
        }

        public java.util.Set<Integer> qux(int i) {
            return java.util.Set.of(i);
        }
    }
}

步骤3:改造测试类

移除静态foo字段和@BeforeAll方法,使用自定义注解标记测试类:

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.assertj.core.api.Assertions.assertThat;

// 指定重复次数为5次,可按需修改
@RepeatWithSharedFoo(5)
class Test {

    // 不再是静态字段,每轮重复会被重新赋值
    Foo foo;

    @ParameterizedTest
    @ValueSource(ints = {1, 2, 3, 4})
    void testBar(int i) {
        assertThat(foo.bar(i)).hasSize(i);
    }

    @ParameterizedTest
    @ValueSource(ints = {1, 2, 3, 4})
    void testQux(int i) {
        assertThat(foo.qux(i)).contains(i);
    }

    // 替换成你的实际Foo类
    static class Foo {
        public java.util.List<Integer> bar(int i) {
            return java.util.Collections.nCopies(i, 1);
        }

        public java.util.Set<Integer> qux(int i) {
            return java.util.Set.of(i);
        }
    }
}

实现效果

  • 每轮重复开始时,prepareTestInstance方法会被调用,重新初始化foo实例
  • 同一轮重复中的所有testBar和testQux参数化测试,都会使用同一个foo实例
  • 测试报告中会清晰显示每轮重复的编号,以及每个参数化测试的执行情况

替代方案(无需自定义扩展)

如果不想自定义扩展,可以用嵌套类结合@RepeatedTest实现:

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.TestInstance;
import static org.assertj.core.api.Assertions.assertThat;

class OuterTest {

    // 指定重复次数
    @RepeatedTest(5)
    void executeTestCycle() {
        InnerTest innerTest = new InnerTest();
        // 手动执行所有参数化测试逻辑
        innerTest.testBar(1);
        innerTest.testBar(2);
        innerTest.testBar(3);
        innerTest.testBar(4);
        innerTest.testQux(1);
        innerTest.testQux(2);
        innerTest.testQux(3);
        innerTest.testQux(4);
    }

    @TestInstance(TestInstance.Lifecycle.PER_CLASS)
    static class InnerTest {
        private Foo foo;

        @BeforeAll
        void setUpAll() {
            foo = generateRandomFoo();
        }

        void testBar(int i) {
            assertThat(foo.bar(i)).hasSize(i);
        }

        void testQux(int i) {
            assertThat(foo.qux(i)).contains(i);
        }
    }

    private static Foo generateRandomFoo() {
        return new Foo();
    }

    static class Foo {
        public java.util.List<Integer> bar(int i) {
            return java.util.Collections.nCopies(i, 1);
        }

        public java.util.Set<Integer> qux(int i) {
            return java.util.Set.of(i);
        }
    }
}

这种方法实现简单,但无法直接使用@ParameterizedTest的注解特性(如测试报告中的参数展示),适合快速实现需求的场景。

内容的提问来源于stack exchange,提问作者Борат Сагдиев

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 08:29:53