1. 概述

本文将深入探讨 JUnit 5 引入的 @RepeatedTest 注解。它提供了一种简洁强大的方式,用于编写需要重复执行的测试用例。

⚠️ 如果你想了解更多 JUnit 5 基础知识,可以参考我们之前的文章:JUnit 5 基础教程JUnit 5 完整指南

2. Maven 依赖与环境配置

首先注意:JUnit 5 需要 Java 8 或更高版本。以下是核心 Maven 依赖:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.10.2</version>
    <scope>test</scope>
</dependency>

这是编写 JUnit 5 测试的必备依赖。最新版本可查看 Maven 中央仓库

3. 简单的 @RepeatedTest 示例

创建重复测试非常简单粗暴——直接在测试方法上添加 @RepeatedTest 注解:

@RepeatedTest(3)
void repeatedTest(TestInfo testInfo) {
    System.out.println("Executing repeated test");
 
    assertEquals(2, Math.addExact(1, 1), "1 + 1 should equal 2");
}

✅ 关键点:

  • 使用 @RepeatedTest 替代标准的 @Test 注解
  • 上述测试将执行 3 次(相当于复制粘贴了 3 次相同测试)

测试报告(IDE 的 JUnit 标签页或报告文件)会显示所有执行记录:

repetition 1 of 3(repeatedTest(TestInfo))
repetition 2 of 3(repeatedTest(TestInfo))
repetition 3 of 3(repeatedTest(TestInfo))

4. 设置 failureThreshold

JUnit Jupiter 5.10 版本为 @RepeatedTest 新增了 failureThreshold 属性。它允许我们设定:当测试失败达到指定次数后,自动跳过剩余重复执行。

⚠️ 注意:

  • 默认值为 Integer.MAX_VALUE
  • failureThreshold 必须是正整数且小于重复次数
  • 特别适用于偶尔因随机性或系统抖动导致失败的测试("踩坑"场景)

下面是一个测试随机数的示例,失败 2 次后终止执行:

@RepeatedTest(value = 10, failureThreshold = 2)
void whenGeneratingRandomNumber_thenNumberShouldBeWithinRange() {
    int number = random.nextInt(10);
    assertTrue(number < 8);
}

代码逻辑:

  • 随机生成 0-9 的整数
  • 断言数字必须小于 8
  • 设置重复 10 次,失败阈值 2 次

执行结果示例(失败 2 次后终止): random unit test with failure threshold

💡 没有设置阈值时,任何一次失败都会导致整个测试失败。设置阈值后可以避免不必要的重复执行。

5. @RepeatedTest 的生命周期支持

每次 @RepeatedTest 执行都相当于独立 @Test,完整支持 JUnit 生命周期:

  • @BeforeEach@AfterEach 会在每次重复执行前后调用

添加生命周期方法验证:

@BeforeEach
void beforeEachTest() {
    System.out.println("Before Each Test");
}

@AfterEach
void afterEachTest() {
    System.out.println("After Each Test");
    System.out.println("=====================");
}

运行测试后控制台输出:

Before Each Test
Executing repeated test
After Each Test
=====================
Before Each Test
Executing repeated test
After Each Test
=====================
Before Each Test
Executing repeated test
After Each Test
=====================

✅ 显然:**@BeforeEach 和 @AfterEach 在每次重复执行时都会被调用**

6. 配置测试名称

默认测试报告名称不够直观?通过 name 属性自定义:

使用内置命名模式

@RepeatedTest(value = 3, name = RepeatedTest.LONG_DISPLAY_NAME)
void repeatedTestWithLongName() {
    System.out.println("Executing repeated test with long name");
 
    assertEquals(2, Math.addExact(1, 1), "1 + 1 should equal 2");
}

输出:

repeatedTestWithLongName() :: repetition 1 of 3(repeatedTestWithLongName())
repeatedTestWithLongName() :: repetition 2 of 3(repeatedTestWithLongName())
repeatedTestWithLongName() :: repetition 3 of 3(repeatedTestWithLongName())

使用短名称模式

@RepeatedTest(value = 3, name = RepeatedTest.SHORT_DISPLAY_NAME)
void repeatedTestWithShortName() {
    // 测试代码
}

输出:

repetition 1 of 3(repeatedTestWithShortName())
repetition 2 of 3(repeatedTestWithShortName())
repetition 3 of 3(repeatedTestWithShortName())

完全自定义名称

@RepeatedTest(value = 3, name = "Custom name {currentRepetition}/{totalRepetitions}")
void repeatedTestWithCustomDisplayName(TestInfo testInfo) {
    assertEquals(2, Math.addExact(1, 1), "1 + 1 should equal 2");
}

输出:

Custom name 1/3(repeatedTestWithCustomDisplayName())
Custom name 2/3(repeatedTestWithCustomDisplayName())
Custom name 3/3(repeatedTestWithCustomDisplayName())

📌 占位符说明:

  • {currentRepetition}:当前重复次数
  • {totalRepetitions}:总重复次数
  • JUnit 运行时自动填充,无需额外配置

7. 访问 RepetitionInfo

除了名称配置,JUnit 还允许在测试代码中获取重复元数据。只需在方法参数中添加 RepetitionInfo

@RepeatedTest(3)
void repeatedTestWithRepetitionInfo(RepetitionInfo repetitionInfo) {
    System.out.println("Repetition #" + repetitionInfo.getCurrentRepetition());
 
    assertEquals(3, repetitionInfo.getTotalRepetitions());
}

控制台输出:

Repetition #1
Repetition #2
Repetition #3

⚠️ 注意:

  • RepetitionInfoRepetitionInfoParameterResolver 提供
  • 仅在 @RepeatedTest 上下文中可用

8. 总结

本文系统介绍了 JUnit 5 的 @RepeatedTest 注解及其配置方式:

✅ 核心能力:

  • 简单实现测试重复执行
  • 灵活配置失败阈值避免无效执行
  • 完整支持生命周期方法
  • 可自定义测试报告名称
  • 提供重复元数据访问接口

📖 完整源代码请访问 GitHub 仓库


原始标题:A Guide to @RepeatedTest in Junit 5