如何在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,提问作者Борат Сагдиев
相关产品推荐
相关产品推荐

