基础写法(@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 体系了。
