Skip to content

基础写法(@Test/断言/生命周期)见《JUnit 5 快速入门》。这篇是进阶特性:同一个逻辑测多组数据、嵌套组织测试、控制超时、扩展框架——单测写得更少、更准。

一、参数化测试(@ParameterizedTest)——一组数据跑一个方法

最常用的进阶特性:同一个断言逻辑,喂多组输入,不用写多个 @Test:

java
@ParameterizedTest
@ValueSource(ints = { 0, 1, 10, 100 })
void 判断正负数(int num) {
    assertTrue(num >= 0 ? true : false);
}

// 多参数用 @CsvSource(字符串转类型)
@ParameterizedTest
@CsvSource({
    "1, 2, 3",
    "5, 5, 10",
    "-1, 1, 0"
})
void 加法(int a, int b, int expected) {
    assertEquals(expected, a + b);
}

// 复杂数据用 @MethodSource(方法返回流)
@ParameterizedTest
@MethodSource("provideUsers")
void 用户校验(User user, boolean valid) {
    assertEquals(valid, user.getName() != null && !user.getName().isEmpty());
}

static Stream<Arguments> provideUsers() {
    return Stream.of(
        Arguments.of(new User("BinMaker"), true),
        Arguments.of(new User(""), false),
        Arguments.of(new User(null), false)
    );
}

好处:一个方法测 N 组数据,报告里每组独立显示(挂哪组看哪组)——边界值全覆盖(0、负数、最大值、空值)。

二、嵌套测试(@Nested)——把测试按场景分组

多个相关测试组织成树状结构,报告清晰:

java
class OrderServiceTest {

    @Nested
    class 创建订单 {

        @Test
        void 库存足够_创建成功() { ... }

        @Test
        void 库存不足_抛异常() { ... }
    }

    @Nested
    class 取消订单 {

        @Test
        void 已支付订单_取消需退款() { ... }

        @Test
        void 未支付订单_直接取消() { ... }
    }
}

好处:测试报告像目录一样分组(创建订单/取消订单),运行结果一眼看清哪类功能有问题。

三、超时控制(@Timeout)

防死循环/慢查询把测试卡死:

java
@Test
@Timeout(2)   // 2 秒内必须跑完,否则失败
void 查询不能太慢() {
    service.query();
}

// 也支持时间单位
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)

适用:性能敏感方法、防死锁、防外部依赖假死。

四、断言进阶

java
// 组合断言:所有断言都执行,一起报告(不挂第一个就停)
assertAll("用户字段",
    () -> assertEquals("BinMaker", user.getName()),
    () -> assertTrue(user.getAge() > 0),
    () -> assertNotNull(user.getEmail())
);

// 断言异常(JDK 8 之前的 @Test(expected) 更优雅的替代)
Exception ex = assertThrows(IllegalArgumentException.class,
    () -> service.createUser(null));
assertEquals("name 不能为空", ex.getMessage());

// 断言超时(方法真跑了,测耗时)
assertTimeout(Duration.ofSeconds(2), () -> service.query());

五、扩展机制(@ExtendWith)

JUnit 5 用 @ExtendWith 挂扩展(替代 JUnit 4 的 @RunWith):

java
@ExtendWith(MockitoExtension.class)   // Mockito 扩展(见《Mockito 快速入门》)
class UserServiceTest { ... }

// Spring 集成测试扩展
@ExtendWith(SpringExtension.class)
class IntegrationTest { ... }

常用扩展

扩展作用
MockitoExtension支持 @Mock/@InjectMocks
SpringExtension支持 Spring 测试(@SpringBootTest 内部已带)
TempDir自动创建/清理临时目录(测文件操作)
java
@Test
void 写文件到临时目录(@TempDir Path tempDir) {
    Path file = tempDir.resolve("test.txt");
    Files.writeString(file, "hello");
    assertEquals("hello", Files.readString(file));
}

六、重复测试与动态测试

java
@RepeatedTest(5)   // 同一个测试跑 5 次(测随机/并发问题)
void 重复执行() { ... }

// 动态测试(运行时生成测试,少用)
@TestFactory
List<DynamicTest> dynamicTests() {
    return List.of(
        DynamicTest.dynamicTest("case1", () -> assertEquals(1, 1)),
        DynamicTest.dynamicTest("case2", () -> assertEquals(2, 2))
    );
}

七、测试报告怎么看

跑完 mvn test 后:

  • 每个 @Test/@ParameterizedTest 是一行结果
  • 参数化测试:每组参数独立一行(FAIL 会标明是哪组参数)
  • 覆盖率报告见《测试覆盖率 JaCoCo

小结

特性一句话
@ParameterizedTest一组数据测一个方法(@ValueSource/@CsvSource/@MethodSource)
@Nested测试按业务场景分组,报告清晰
@Timeout防测试卡死
assertAll / assertThrows组合断言 + 优雅断言异常
@ExtendWith挂扩展(Mockito/Spring)
@TempDir临时目录测试文件操作

基础 + 进阶 + Mockito(《Mockito 快速入门》)就是完整的 JUnit 5 体系了。