Article

测试框架 JUnit 5

更新于:2026-07-15

第一章:JUnit 入门基础

1.1 JUnit 简介与版本演进

本小节介绍 JUnit 的基本定位及其主要版本差异。

概念名称说明注意事项
JUnitJava 语言中最广泛使用的单元测试框架,用于验证代码逻辑的正确性。需配合构建工具(如 Maven/Gradle)和 IDE 使用。
JUnit 3基于继承 TestCase 类,使用命名约定(如 testXxx())识别测试方法。已淘汰,不推荐新项目使用。
JUnit 4引入注解(如 @Test),支持参数化测试、假设(Assume)等,依赖 Java 5+。仍被部分老项目使用,但官方已停止主要维护。
JUnit 5 (Jupiter)模块化架构(Platform + Jupiter + Vintage),支持 Lambda、动态测试、扩展模型等,需 Java 8+。当前主流版本,推荐新项目采用。
JUnit PlatformJUnit 5 的底层测试引擎,支持其他测试框架(如 Spek、Kotest)在其上运行。开发者通常无需直接操作 Platform。
JUnit Vintage用于在 JUnit 5 环境中运行 JUnit 3/4 的测试用例。仅在迁移旧测试时需要引入。

1.2 环境搭建与依赖配置

本小节说明如何在 Maven 和 Gradle 项目中配置 JUnit 5。

Maven 项目配置步骤

步骤名称操作细节注意事项
添加 JUnit Jupiter API 依赖pom.xml 中添加:版本建议使用最新稳定版(如 5.10.0)。scope 必须为 test
添加 Surefire 插件(可选但推荐)pom.xml 中添加:若未配置,Maven 可能无法自动发现 JUnit 5 测试。
验证 Java 版本确保项目 JDK ≥ 8JUnit 5 不支持 Java 7 及以下。

Maven 依赖配置示例:

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

Maven Surefire 插件配置示例:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.0.0-M9</version>
</plugin>

Gradle 项目配置步骤

步骤名称操作细节注意事项
应用 Java 插件build.gradle 中包含:apply plugin: 'java'plugins { id 'java' }必须启用 Java 插件才能编译测试代码。
添加 JUnit Jupiter 依赖dependencies 块中添加:使用 testImplementation 而非 testCompile(后者已废弃)。
启用 JUnit Platformbuild.gradle 中添加:若省略此配置,Gradle 默认使用 JUnit 4,导致测试不执行。

Gradle 配置示例:

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
}

test {
    useJUnitPlatform()
}

1.3 第一个 JUnit 测试用例

本小节展示如何编写并运行最简单的 JUnit 5 测试类。

方法名称语法用途代码示例注意事项
@Test 注解方法public void methodName() { ... }标识一个测试方法见下方代码块方法必须是 non-private、non-static;返回类型必须为 void;建议使用 void 而非 public(JUnit 5 支持 package-private)。
Assertions.assertEqualsassertEquals(expected, actual)断言两个值相等assertEquals(10, 5 + 5);若 expected 与 actual 不等,测试失败并抛出 AssertionError
运行测试(IDE 方式)在 IDE 中右键测试类 → Run ‘CalculatorTest’执行测试用例——需确保 IDE 已正确识别 JUnit 5(IntelliJ IDEA 2019.2+ / Eclipse 2018-12+ 原生支持)。
运行测试(命令行)mvn test(Maven)或 ./gradlew test(Gradle)通过构建工具批量执行——需先完成 1.2 节的依赖与插件配置,否则可能跳过测试。

测试类示例:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class CalculatorTest {
    @Test
    void testAdd() {
        assertEquals(4, 2 + 2);
    }
}

第二章:核心注解与生命周期

2.1 @Test 注解详解

@Test 是 JUnit 5 中标识测试方法的核心注解。

方法/注解名称语法用途代码示例注意事项
@Test在方法上标注 @Test标识一个独立的测试用例方法见下方代码块方法不能是 privatestatic;返回类型必须为 void;可抛出任何异常(但未捕获的异常会导致测试失败)。
自定义显示名称@Test + @DisplayName("自定义名称")在测试报告或 IDE 中显示更友好的名称@DisplayName("加法运算应返回正确结果")@DisplayName 支持空格、中文、表情符号等,仅用于展示,不影响执行。
测试方法命名建议使用描述性名称,如 shouldDoXxxWhenYyy()提高可读性和可维护性void shouldThrowExceptionWhenDivideByZero() { ... }避免使用 testXxx 命名(JUnit 3 风格),推荐行为驱动命名。

@Test 基本使用示例:

import org.junit.jupiter.api.Test;

class ExampleTest {
    @Test
    void shouldReturnTrue() {
        assert true;
    }
}

@DisplayName 使用示例:

@Test
@DisplayName("加法运算应返回正确结果")
void testAddition() {
    assertEquals(4, 2 + 2);
}

2.2 生命周期注解(@BeforeEach, @AfterEach, @BeforeAll, @AfterAll)

这些注解控制测试类中方法的执行时机,用于初始化和清理资源。

注解名称语法用途代码示例注意事项
@BeforeEach在 non-static 方法上标注 @BeforeEach每个 @Test 执行前运行一次见下方代码块方法必须是非静态的;可有多个 @BeforeEach 方法(执行顺序不确定,不推荐)。
@AfterEach在 non-static 方法上标注 @AfterEach每个 @Test 执行后运行一次见下方代码块用于释放资源(如关闭连接、删除临时文件);即使测试失败也会执行。
@BeforeAll在 static 方法上标注 @BeforeAll整个测试类开始前运行一次见下方代码块方法必须是 static(或配合 @TestInstance(Lifecycle.PER_CLASS) 可为非静态);用于昂贵的一次性初始化(如启动服务器)。
@AfterAll在 static 方法上标注 @AfterAll整个测试类结束后运行一次见下方代码块同样需为 static(除非使用 PER_CLASS 实例生命周期);即使部分测试失败也会执行。
@TestInstance(Lifecycle.PER_CLASS)在测试类上标注改变测试实例生命周期为”每个类一个实例”见下方代码块默认是 PER_METHOD(每个测试方法新建实例);PER_CLASS 可提升性能,但需注意状态污染风险。

完整生命周期示例:

import org.junit.jupiter.api.*;

class LifecycleTest {

    @BeforeAll
    static void init() {
        System.setProperty("env", "test");
    }

    @BeforeEach
    void setUp() {
        database = new InMemoryDB();
        database.connect();
    }

    @AfterEach
    void tearDown() {
        if (database != null) database.close();
    }

    @AfterAll
    static void cleanup() {
        TempDir.deleteAll();
    }

    @Test
    void testSomething() {
        // ...
    }
}

PER_CLASS 生命周期示例:

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MyTest {
    @BeforeAll
    void init() { ... } // 可为非静态
}

2.3 禁用与条件执行(@Disabled, @EnabledOnOs 等)

JUnit 5 提供多种条件注解,用于在特定环境下启用或跳过测试。

注解名称语法用途代码示例注意事项
@Disabled@Disabled@Disabled("原因说明")永久禁用某个测试(相当于注释掉)@Disabled("等待修复 #123")被禁用的测试不会执行,但在报告中标记为”skipped”。
@EnabledOnOs@EnabledOnOs({OS.WINDOWS})仅在指定操作系统上运行@EnabledOnOs(OS.LINUX)OS 枚举包括 WINDOWSLINUXMACOTHER;多系统用数组。
@DisabledOnOs@DisabledOnOs(OS.MAC)在指定操作系统上跳过测试@DisabledOnOs(OS.MAC)@EnabledOnOs 互斥,避免同时使用。
@EnabledOnJre@EnabledOnJre(JRE.JAVA_17)仅在指定 Java 版本运行@EnabledOnJre(JRE.JAVA_11)JRE 枚举包括 JAVA_8JAVA_21 等;适用于验证版本兼容性。
@DisabledOnJre@DisabledOnJre(JRE.JAVA_8)在指定 Java 版本跳过@DisabledOnJre(JRE.JAVA_8)——
@EnabledIfSystemProperty@EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*")根据系统属性启用见下方代码块matches 使用正则表达式;若属性不存在,测试被禁用。
@EnabledIfEnvironmentVariable@EnabledIfEnvironmentVariable(named = "ENV", matches = "prod")根据环境变量启用@EnabledIfEnvironmentVariable(named = "CI", matches = "true")适用于 CI/CD 环境中的特殊测试。
@EnabledIf / @DisabledIf(自定义)@EnabledIf("customCondition")通过自定义逻辑控制见下方代码块条件方法必须是 static boolean,且无参数;慎用,避免引入外部依赖导致不可靠。

条件执行示例:

@Test
@EnabledOnOs(OS.LINUX)
void linuxOnlyTest() { ... }

@Test
@DisabledOnOs(OS.MAC)
void nonMacTest() { ... }

@Test
@EnabledIfSystemProperty(named = "user.country", matches = "CN")
void chinaSpecificTest() { ... }

// 自定义条件
static boolean isFastNetwork() {
    return Network.speed() > 100;
}

@Test
@EnabledIf("isFastNetwork")
void fastNetworkTest() { ... }

第三章:断言机制(Assertions)

3.1 基本断言方法(assertEquals, assertTrue 等)

这些是最常用的断言方法,用于验证逻辑结果是否符合预期。

方法名称语法用途代码示例注意事项
assertEqualsassertEquals(expected, actual) / assertEquals(expected, actual, message)断言两个值相等(使用 equals() 比较)assertEquals(5, 2 + 3);对于浮点数,应使用带 delta 的重载:assertEquals(0.1, result, 0.001)message 支持 Lambda 表达式(延迟求值)。
assertNotEqualsassertNotEquals(unexpected, actual)断言两个值不相等assertNotEquals(0, list.size());同样使用 equals() 判断不等。
assertTrueassertTrue(condition) / assertTrue(condition, message)断言条件为 trueassertTrue(user.isActive());message 推荐使用 Lambda 避免不必要的字符串拼接开销。
assertFalseassertFalse(condition)断言条件为 falseassertFalse(str.isBlank());——
assertNullassertNull(actual)断言对象为 nullassertNull(cache.get("invalid_key"));——
assertNotNullassertNotNull(actual) / assertNotNull(actual, message)断言对象非 nullassertNotNull(result, "计算结果不应为 null");常用于验证工厂方法或解析器返回值。
assertSameassertSame(expected, actual)断言两个引用指向同一对象(== 比较)assertSame(singletonInstance, getInstance());适用于单例、缓存等场景。
assertNotSameassertNotSame(unexpected, actual)断言两个引用不指向同一对象assertNotSame(new Object(), obj);——

基本断言使用示例:

assertEquals(5, 2 + 3);
assertEquals("Hello", str.trim(), "字符串应无前后空格");
assertTrue(list.isEmpty(), () -> "列表应为空,但实际大小为:" + list.size());
assertFalse(str.isBlank());
assertNull(cache.get("invalid_key"));
assertNotNull(result, "计算结果不应为 null");

3.2 异常断言(assertThrows, assertDoesNotThrow)

用于验证代码是否按预期抛出(或不抛出)异常。

方法名称语法用途代码示例注意事项
assertThrowsassertThrows(expectedType, executable)断言执行代码块时抛出指定类型的异常见下方代码块executableThrowingRunnable(无参无返回 Lambda);必须捕获返回的异常以进一步验证消息或原因。
assertDoesNotThrowassertDoesNotThrow(executable) / assertDoesNotThrow(executable, message)断言代码块不抛出任何异常见下方代码块若需获取返回值,可将结果赋给变量;适用于验证”正常路径”无异常。
assertThrows 与泛型可配合泛型精确匹配子类异常assertThrows(FileNotFoundException.class, () -> new FileInputStream("missing.txt"));不要使用父类(如 Exception)断言,会降低测试精度。若抛出的是子类异常但断言父类,测试仍通过,但可能掩盖问题。

异常断言示例:

// assertThrows - 验证异常类型和消息
IllegalArgumentException ex = assertThrows(
    IllegalArgumentException.class,
    () -> calculator.divide(10, 0)
);
assertEquals("除数不能为零", ex.getMessage());

// assertDoesNotThrow - 验证不抛异常并获取返回值
String result = assertDoesNotThrow(
    () -> service.process(input)
);

3.3 超时断言(assertTimeout)

用于确保代码在指定时间内完成执行。

方法名称语法用途代码示例注意事项
assertTimeoutassertTimeout(Duration timeout, executable)断言代码在超时前完成(在当前线程执行)见下方代码块executable 可返回值;若超时,抛出 AssertionFailedError;任务在主线程运行,可访问局部变量。
assertTimeoutPreemptivelyassertTimeoutPreemptively(Duration timeout, executable)断言代码在超时前完成(在独立线程执行,超时可中断)见下方代码块适用于可能死锁或无限循环的代码;但因跨线程,无法访问外部非 final 变量;中断不保证立即生效。
Duration 构造Duration.ofSeconds(1), Duration.ofMillis(100)创建时间间隔Duration.ofMinutes(1)使用 java.time.Duration,避免使用毫秒整数(易错)。

超时断言示例:

// 在主线程执行,超时抛 AssertionFailedError
assertTimeout(Duration.ofSeconds(2), () -> {
    Thread.sleep(1000);
    return heavyComputation();
});

// 在独立线程执行,可中断死循环
assertTimeoutPreemptively(Duration.ofMillis(500), () -> {
    while (true) { /* 死循环 */ }
});

3.4 组合断言(assertAll)

允许在一个测试中执行多个断言,并在最后统一报告所有失败(而非第一个失败就终止)。

方法名称语法用途代码示例注意事项
assertAllassertAll(Stream<Executable> executables) / assertAll(String heading, Executable...)批量执行断言,收集所有失败见下方代码块每个断言必须包装为 Executable(即无参无返回 Lambda);heading 用于错误报告分组;即使部分失败,其余断言仍会执行。
嵌套 assertAllassertAll 内部再调用 assertAll构建层次化断言结构见下方代码块有助于组织复杂对象的验证逻辑;错误信息更具结构性。
与异常结合assertAll 中可包含 assertThrows组合验证见下方代码块所有 Executable 均可包含任意断言逻辑;但注意异常断言本身不抛出异常(它验证异常是否被抛出)。

组合断言示例:

// 基本组合断言
assertAll(
    "用户信息验证",
    () -> assertEquals("张三", user.getName()),
    () -> assertEquals(25, user.getAge()),
    () -> assertTrue(user.isVerified())
);

// 嵌套 assertAll
assertAll(
    "地址验证",
    () -> assertAll("省市区",
        () -> assertNotNull(addr.province),
        () -> assertNotNull(addr.city)
    ),
    () -> assertNotNull(addr.street)
);

// 与异常结合
assertAll(
    () -> assertEquals(1, list.size()),
    () -> assertThrows(NullPointerException.class, () -> list.add(null))
);

第四章:参数化测试(Parameterized Tests)

4.1 @ParameterizedTest 基础用法

@ParameterizedTest 是 JUnit 5 中实现数据驱动测试的核心注解,允许一个测试方法被多次调用,每次使用不同参数。

注解/概念名称语法用途代码示例注意事项
@ParameterizedTest在方法上标注 @ParameterizedTest,并配合参数源注解标识一个参数化测试方法见下方代码块方法必须声明与参数源匹配的参数列表;不能与 @Test 同时使用;需引入 junit-jupiter-params 模块(Maven/Gradle 默认包含在 junit-jupiter 中)。
显示名称模板@ParameterizedTest(name = "[{index}] {arguments}")自定义每次测试运行的显示名称@ParameterizedTest(name = "输入 {0} 应为偶数")占位符:{index}:从 1 开始的序号;{arguments}:全部参数;{0}, {1}…:单个参数。
测试方法签名void methodName(Type param1, Type param2, ...)接收来自参数源的数据见下方代码块参数数量和类型必须与参数源严格匹配;支持基本类型、String、枚举、对象等。

@ParameterizedTest 基本示例:

@ParameterizedTest
@ValueSource(ints = {1, 2, 3})
void testIsPositive(int n) {
    assertTrue(n > 0);
}

自定义显示名称示例:

@ParameterizedTest(name = "输入 {0} 应为偶数")
@ValueSource(ints = {2, 4, 6})
void testEven(int n) { ... }

多参数签名示例:

@ParameterizedTest
@CsvSource({"a,1", "b,2"})
void testPair(String s, int i) { ... }

4.2 参数来源(@ValueSource, @CsvSource, @MethodSource 等)

JUnit 5 提供多种内置注解作为参数来源,满足不同场景需求。

参数源注解语法用途代码示例注意事项
@ValueSource@ValueSource(ints = {1,2}) / @ValueSource(strings = {"a","b"}) / @ValueSource(classes = {String.class})提供单一类型的一维数组字面量@ValueSource(strings = {"hello", "world"})支持类型:shorts, bytes, ints, longs, floats, doubles, chars, booleans, strings, classes;不支持对象或复杂结构。
@EnumSource@EnumSource(TimeUnit.class) / @EnumSource(names = {"SECONDS", "MINUTES"})使用枚举常量作为参数@EnumSource(TimeUnit.class)可通过 namesmode 过滤(如 EXCLUDE);适用于覆盖所有枚举值的测试。
@CsvSource@CsvSource({"1, a", "2, b"}) / @CsvSource(value = "foo; bar", delimiter = ';')从 CSV 格式字符串提供多参数组合@CsvSource({"3, odd", "4, even"})默认逗号分隔;空值用 null 表示(如 "1, null");支持转义引号;每行对应一次测试调用。
@CsvFileSource@CsvFileSource(resources = "/data.csv")从类路径下的 CSV 文件读取参数@CsvFileSource(resources = "/test-cases.csv", numLinesToSkip = 1)resources 路径相对于 src/test/resourcesnumLinesToSkip 可跳过标题行;文件编码默认 UTF-8。
@MethodSource@MethodSource("methodName")从本地或外部静态方法获取参数流见下方代码块方法必须返回 StreamIterableIterator 或数组;可位于当前类或指定类(如 "com.example.TestData#values");方法必须是 static
@ArgumentsSource@ArgumentsSource(MyProvider.class)使用自定义 ArgumentsProvider 实现见下方代码块适用于复杂参数生成逻辑;需实现 ArgumentsProvider 接口;比 @MethodSource 更灵活但更冗长。

@MethodSource 示例:

@ParameterizedTest
@MethodSource("provideStrings")
void testWithMethodSource(String s) { ... }

static Stream<String> provideStrings() {
    return Stream.of("x", "y");
}

@ArgumentsSource 示例:

@ParameterizedTest
@ArgumentsSource(RangeProvider.class)
void testRange(int n) { ... }

static class RangeProvider implements ArgumentsProvider {
    public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
        return IntStream.range(0, 5).mapToObj(Arguments::of);
    }
}

4.3 自定义参数转换器(ArgumentConverter)

当参数源提供的原始数据类型与测试方法参数类型不匹配时,可通过自定义转换器进行转换。

概念/注解名称语法用途代码示例注意事项
@ConvertWith@ParameterizedTest + @ValueSource(strings = {"2023-01-01"})指定单个参数的转换器见下方完整示例仅作用于标注的参数;转换器必须实现 ArgumentConverter 接口。
ArgumentConverterpublic class LocalDateConverter implements ArgumentConverter将原始参数(如 String)转换为目标类型(如 LocalDate)见下方完整示例转换器方法必须 public;若转换失败应抛出异常(测试将失败);不支持泛型自动推导,需显式指定。
全局转换器(通过扩展)通过注册全局 ArgumentConverter 扩展对所有匹配类型的参数自动转换需实现 ParameterResolverArgumentConverter 并注册为 ExtensionJUnit 5 默认不支持全局自动转换;通常推荐使用 @ConvertWith 显式声明,提高可读性。
内置转换支持JUnit 5 自动支持部分类型转换(如 String → enum, primitive ↔ wrapper)无需自定义转换器@ValueSource(strings = {"RED", "BLUE"}) + void testColor(Color c) { ... }(Color 为枚举)自动转换有限;复杂类型(如 JSON 字符串 → POJO)仍需自定义转换器。

自定义转换器完整示例:

class DateTest {
    @ParameterizedTest
    @ValueSource(strings = {"2023-01-01", "2024-12-31"})
    void testHoliday(@ConvertWith(LocalDateConverter.class) LocalDate date) {
        assertNotNull(date);
    }

    static class LocalDateConverter implements ArgumentConverter {
        public Object convert(Object source, ParameterContext context) {
            if (source instanceof String) {
                return LocalDate.parse((String) source);
            }
            throw new IllegalArgumentException("无法转换");
        }
    }
}

第五章:测试套件与分组

5.1 @Suite 与测试套件构建

JUnit 5 通过 @Suite 注解和相关选择器注解,支持将多个测试类组合为一个逻辑测试套件,便于批量执行。

注解/概念名称语法用途代码示例注意事项
@Suite在空类上标注 @Suite 及选择器注解定义一个测试套件入口类见下方代码块套件类本身不包含测试方法;必须配合至少一个选择器注解(如 @SelectClasses);需引入 junit-platform-suite 引擎依赖。
所需依赖(Maven)——提供 @Suite 支持见下方代码块若未添加此依赖,运行套件会报 ClassNotFoundException 或被忽略。
所需依赖(Gradle)testImplementation 'org.junit.platform:junit-platform-suite:1.10.0'同上——同样需在 build.gradle 中显式声明。
套件执行方式IDE:右键运行套件类 / Maven:mvn test -Dtest=ServiceTestSuite / Gradle:./gradlew test --tests "ServiceTestSuite"执行整个套件中的所有测试——套件类名需符合构建工具的测试类命名规则(通常含 Test/Suite)。
嵌套套件不支持直接嵌套 @Suite无法在一个套件中包含另一个套件类——可通过标签(@Tag)或包结构间接实现分层组织。

@Suite 套件示例:

import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;

@Suite
@SelectClasses({UserServiceTest.class, OrderServiceTest.class})
public class ServiceTestSuite {
    // 空类即可
}

Maven 依赖配置:

<dependency>
    <groupId>org.junit.platform</groupId>
    <artifactId>junit-platform-suite</artifactId>
    <version>1.10.0</version>
    <scope>test</scope>
</dependency>

5.2 按类/包/标签选择测试(@SelectClasses, @IncludeTags 等)

JUnit Suite API 提供多种选择器注解,用于灵活组合测试用例。

选择器注解语法用途代码示例注意事项
@SelectClasses@SelectClasses({A.class, B.class})显式指定要包含的测试类见下方代码块类必须是有效的 JUnit 测试类;可混合 JUnit 5 和 Vintage(JUnit 4)测试类。
@SelectPackages@SelectPackages("com.example.service")包含指定包及其子包下的所有测试类见下方代码块递归扫描子包;适用于按模块组织测试的项目。
@SelectClassNamePatterns@SelectClassNamePatterns({"*IntegrationTest", "*SmokeTest"})通过正则表达式匹配类名@SelectClassNamePatterns("**/*IT.java")模式使用 Ant 风格通配符(* 匹配任意字符,** 匹配路径);注意转义特殊字符。
@IncludeTags / @ExcludeTags@IncludeTags("fast") / @ExcludeTags("slow")基于 @Tag 标签过滤测试见下方代码块需配合测试类或方法上的 @Tag("database") 使用;@IncludeTags 是”或”关系(满足任一即包含),@ExcludeTags 优先级更高。
@Tag(测试侧)@Tag("integration")为测试类或方法打标签见下方代码块标签名区分大小写;推荐使用小写+连字符(如 "db-integration");标签可叠加。
@IncludeEngines / @ExcludeEngines@IncludeEngines("junit-jupiter")仅包含指定测试引擎的测试@IncludeEngines("junit-vintage")引擎 ID:junit-jupiter(JUnit 5)、junit-vintage(JUnit 3/4);适用于混合测试环境。
组合使用示例多个选择器可同时使用实现复杂筛选逻辑见下方代码块选择器之间是”与”关系:必须同时满足所有条件;例如,类必须在指定包中 且 带有 “critical” 标签 且 不带 “broken” 标签。

@SelectClasses / @SelectPackages 示例:

// 按类选择
@Suite
@SelectClasses({LoginTest.class, ProfileTest.class})
class AuthTestSuite {}

// 按包选择
@Suite
@SelectPackages("com.example.api")
class ApiTestSuite {}

@Tag 与 @IncludeTags / @ExcludeTags 示例:

// 测试侧 - 打标签
@Tag("security")
class SecurityTest {
    @Test
    @Tag("ldap")
    void testLdapAuth() { ... }
}

// 套件侧 - 按标签筛选
@Suite
@IncludeTags("database")
@SelectPackages("com.example")
class DatabaseTestSuite {}

组合使用示例:

@Suite
@SelectPackages("com.example")
@IncludeTags("critical")
@ExcludeTags("broken")
class CriticalTestSuite {}

标签使用建议:

常见标签:fastslowintegrationdatabaseexternalsmokeregression。构建脚本中可通过系统属性动态启用标签(如 Maven Surefire 的 groups/excludedGroups)。


第六章:扩展模型(Extensions)

6.1 扩展接口概览(BeforeEachCallback, AfterTestExecutionCallback 等)

JUnit 5 提供多个扩展点接口,允许在测试生命周期各阶段插入自定义逻辑。

扩展接口名称触发时机核心方法用途注意事项
BeforeAllCallback@BeforeAll 方法前执行(整个测试类开始前)void beforeAll(ExtensionContext context)全局初始化(如启动嵌入式数据库)需配合 @ExtendWith 使用;若抛出异常,整个测试类被跳过。
AfterAllCallback@AfterAll 方法后执行(整个测试类结束后)void afterAll(ExtensionContext context)全局清理(如关闭服务器)即使测试失败也会执行;注意资源释放顺序。
BeforeEachCallback@BeforeEach 方法前执行(每个测试方法前)void beforeEach(ExtensionContext context)方法级初始化(如重置状态)执行顺序:BeforeAllBeforeEachTest;可访问当前测试方法信息。
AfterEachCallback@AfterEach 方法后执行(每个测试方法后)void afterEach(ExtensionContext context)方法级清理(如回滚事务)执行顺序:TestAfterEachAfterEachCallback;即使测试失败也会执行。
BeforeTestExecutionCallback在测试方法体执行前(@BeforeEach 之后)void beforeTestExecution(ExtensionContext context)性能监控、日志记录等位于实际测试逻辑之前,适合 AOP 式增强。
AfterTestExecutionCallback在测试方法体执行后(无论成功/失败)void afterTestExecution(ExtensionContext context)捕获测试结果、生成报告可通过 context.getExecutionException() 判断是否失败。
TestExecutionExceptionHandler当测试方法抛出异常时void handleTestExecutionException(ExtensionContext context, Throwable throwable)自定义异常处理(如重试、转换)若处理后仍抛出异常,测试标记为失败;可吞掉异常使测试”通过”(不推荐)。
ParameterResolver为测试方法参数提供值boolean supportsParameter(...) / Object resolveParameter(...)实现依赖注入(如自动注入 Mock 对象)需与 @ExtendWith 配合;常用于替代构造器/字段注入。
TestWatcher监听测试生命周期事件testSuccessful / testFailed / testAborted记录测试结果、生成审计日志不影响测试流程;适合非侵入式监控。

ExtensionContext 关键上下文对象,可获取:

  • 当前测试类/方法(getRequiredTestClass(), getTestMethod()
  • 测试显示名称(getDisplayName()
  • 存储临时数据(getStore()
  • 获取注解(getElement().getAnnotation(...)

6.2 自定义扩展实现

通过实现上述接口并使用 @ExtendWith 注册,可创建可复用的扩展逻辑。

操作步骤操作细节注意事项
定义扩展类创建类实现一个或多个扩展接口推荐单一职责(如只实现 BeforeEachCallback);类可为 static 或 top-level
注册扩展(类级别)在测试类上标注 @ExtendWith(MyExtension.class)扩展对类中所有测试方法生效;可叠加多个 @ExtendWith({A.class, B.class})
注册扩展(方法级别)在测试方法上标注 @ExtendWith(MyExtension.class)仅对该方法生效;优先级高于类级别
使用 ExtensionContext通过 context 获取测试元数据或存储状态调用 getStore(Namespace) 获取隔离存储空间,避免跨测试污染

计时扩展示例:

public class TimingExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback {
    private static final Namespace NAMESPACE = Namespace.create("timing");

    public void beforeTestExecution(ExtensionContext context) {
        long start = System.currentTimeMillis();
        context.getStore(NAMESPACE).put("start", start);
    }

    public void afterTestExecution(ExtensionContext context) {
        long start = context.getStore(NAMESPACE).get("start", long.class);
        long duration = System.currentTimeMillis() - start;
        System.out.println(context.getDisplayName() + " 耗时: " + duration + "ms");
    }
}

注意:必须使用 Namespace 隔离不同测试的数据;System.out 仅作演示,生产环境应使用日志框架。

方法/注解名称语法用途代码示例注意事项
@ExtendWith@ExtendWith(TimingExtension.class)注册自定义扩展@ExtendWith(TimingExtension.class) class MyTest { @Test void testX() { ... } }可重复注解(Java 8+);也可通过 META-INF/services 机制全局注册(高级用法)
Namespace.createNamespace.create("my-extension")创建唯一命名空间private static final Namespace NS = Namespace.create("cache");避免与其他扩展冲突;建议使用包名作为前缀

6.3 内置扩展(如 MockitoExtension)

JUnit 5 社区和官方提供多个常用内置扩展,简化常见测试场景。

内置扩展名称所属库用途使用方式注意事项
MockitoExtensionorg.mockito:mockito-junit-jupiter自动初始化 @Mock@InjectMocks 字段见下方代码块需添加依赖;字段必须是非 private;支持 @Spy@Captor
SpringExtensionorg.springframework:spring-test集成 Spring Boot 测试(@SpringBootTest@ExtendWith(SpringExtension.class) + @SpringBootTest通常通过 @SpringBootTest 间接启用,无需显式写 @ExtendWith
TemporaryDirectoryJUnit Jupiter 内置自动创建并清理临时目录@Test void test(@TempDir Path tempDir) { ... }@TempDir 可用于参数(ParameterResolver)或字段;目录在测试后自动删除
RepeatedTestJUnit Jupiter 内置(非传统扩展)重复执行测试@RepeatedTest(3) void testFlakyFeature() { ... }虽非 @ExtendWith 形式,但基于扩展模型实现;每次重复视为独立测试
DisabledConditionJUnit Jupiter 内置支撑 @Disabled@EnabledOnOs 等条件注解无需手动注册由 JUnit 自动加载;开发者通常只使用高层注解

MockitoExtension 依赖配置:

<!-- Maven -->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-junit-jupiter</artifactId>
    <version>5.7.0</version>
    <scope>test</scope>
</dependency>

MockitoExtension 使用示例:

import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class ServiceTest {
    @Mock Database db;
    @InjectMocks UserService service;

    @Test void testLogin() { ... }
}

TemporaryDirectory 使用示例:

@Test
void testFileWrite(@TempDir Path tempDir) throws IOException {
    Path file = tempDir.resolve("test.txt");
    Files.write(file, "hello".getBytes());
    assertTrue(Files.exists(file));
}

最佳实践:

  • 优先使用成熟扩展(如 MockitoExtension),避免重复造轮子
  • 自定义扩展应保持无状态或使用 Namespace 隔离状态
  • 扩展逻辑应轻量,避免显著拖慢测试速度

第七章:高级特性与集成

7.1 动态测试(@TestFactory)

@TestFactory 允许在运行时动态生成测试用例,适用于数据驱动但参数无法静态确定的场景。

注解/方法名称语法用途代码示例注意事项
@TestFactory标注返回 StreamCollection 等的方法动态生成多个独立测试实例见下方代码块方法不能是 void;必须返回支持的容器类型(Stream / Collection / Iterable / Iterator);每个 DynamicTest 是独立测试,失败互不影响。
DynamicTest.dynamicTeststatic DynamicTest dynamicTest(String displayName, Executable executable)构造一个动态测试实例dynamicTest("检查正数", () -> assertTrue(value > 0));displayName 支持任意字符串;executable 是无参无返回 Lambda;不支持生命周期回调(如 @BeforeEach 不对其生效)。
返回类型支持Stream, Collection, Iterable, Iterator提供灵活的数据源见下方代码块推荐使用 Stream(惰性求值)避免预加载大量数据;若数据量小,List 更直观。
@ParameterizedTest 区别@ParameterizedTest:编译时已知参数 / @TestFactory:运行时生成测试选择依据:参数是否可静态枚举——若参数来自文件、数据库、网络等运行时源,用 @TestFactory;否则优先用 @ParameterizedTest(支持生命周期、标签等)。

动态测试示例:

import org.junit.jupiter.api.DynamicTest;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;

@TestFactory
Stream<DynamicTest> testFileParsing() {
    return Files.list(Paths.get("test-cases"))
        .map(path -> dynamicTest(
            "解析文件: " + path.getFileName(),
            () -> assertValid(parse(path))
        ));
}

使用 List 返回示例:

@TestFactory
List<DynamicTest> testCases() {
    List<DynamicTest> tests = new ArrayList<>();
    for (String case : cases) {
        tests.add(dynamicTest(case, () -> runTest(case)));
    }
    return tests;
}

7.2 与 Maven / Gradle 集成

正确配置构建工具是批量执行 JUnit 5 测试的前提。

Maven 集成

配置项操作细节注意事项
添加依赖必须包含 junit-jupiter(含 API + params + engine)——
配置 Surefire 插件Surefire ≥ 2.22.0 才原生支持 JUnit Platform;旧版本需额外配置 provider——
执行命令mvn test自动发现并运行所有符合命名规则的测试类(默认 **/*Test.java
指定测试类mvn test -Dtest=UserServiceTest支持通配符:mvn test -Dtest=*ServiceTest
跳过测试mvn install -DskipTests-Dmaven.test.skip=true(同时跳过编译测试代码)

Maven 完整配置示例:

<!-- 依赖 -->
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.10.0</version>
    <scope>test</scope>
</dependency>

<!-- Surefire 插件 -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.0.0-M9</version>
</plugin>

Gradle 集成

配置项操作细节注意事项
应用 Java 插件plugins { id 'java' }必须启用才能识别 src/test/java
添加依赖testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'不需要单独引入 engine,junit-jupiter 已包含
启用 JUnit Platformtest { useJUnitPlatform() }关键步骤! 若省略,Gradle 默认使用 JUnit 4,导致测试不执行
执行命令./gradlew test生成 HTML 报告于 build/reports/tests/test/index.html
过滤测试./gradlew test --tests "*ServiceTest"支持类名或方法名匹配
跳过测试./gradlew build -x test-x 表示 exclude task

Gradle 完整配置示例:

plugins {
    id 'java'
}

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
}

test {
    useJUnitPlatform()
}

7.3 与 IDE(IntelliJ IDEA / VS Code)集成

现代 IDE 原生支持 JUnit 5,提供一键运行、调试和结果可视化。

IDE配置要求使用方式注意事项
IntelliJ IDEA2019.2 或更高版本右键测试类/方法 → Run;点击编辑器左侧绿色 ▶️无需额外插件;自动识别 JUnit 5;若未运行,检查 Project SDK 是否 ≥ Java 8
VS Code安装 Extension Pack for Java(含 Language Support for Java + Debugger)在测试方法上出现 “Run Test” / “Debug Test” CodeLens需确保项目根目录有 pom.xmlbuild.gradle;首次运行可能需下载依赖
Eclipse2018-12 或更高版本右键 → Run As → JUnit Test若提示 “No JUnit tests found”,检查是否启用了 JUnit 5 支持(Project Properties → Java Build Path → Libraries)

通用问题排查:

  • 依赖是否完整
  • JDK 版本是否 ≥ 8
  • 是否误用 JUnit 4 注解
  • 常见错误:ClassNotFoundException(缺依赖)、TestEngine not found(缺 junit-platform-engine
  • 查看 IDE 控制台错误信息定位问题

7.4 生成测试报告(Surefire / Jacoco)

通过构建工具生成结构化测试报告和代码覆盖率分析。

Surefire 测试报告(Maven)

报告类型生成位置内容配置方式
XML 报告target/surefire-reports/TEST-*.xml供 CI 系统(如 Jenkins)解析默认生成,无需额外配置
HTML 报告target/site/surefire-report.html人类可读的测试摘要需运行 mvn surefire-report:report
自定义输出目录target/custom-reports——适用于多模块项目隔离报告

自定义输出目录配置:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <reportsDirectory>${project.build.directory}/custom-reports</reportsDirectory>
    </configuration>
</plugin>

JaCoCo 代码覆盖率

工具配置方式报告位置注意事项
Maven + JaCoCoorg.jacoco:jacoco-maven-plugin:0.8.11target/site/jacoco/index.htmlprepare-agent 必须在 test 前执行;report 生成 HTML 覆盖率报告
Gradle + JaCoCoplugins { id 'jacoco' }build/reports/jacoco/test/html/index.htmlfinalizedBy 确保测试后自动生成报告

Maven JaCoCo 配置:

<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.8.11</version>
    <executions>
        <execution>
            <goals>
                <goal>prepare-agent</goal>
            </goals>
        </execution>
        <execution>
            <id>report</id>
            <phase>test</phase>
            <goals>
                <goal>report</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Gradle JaCoCo 配置:

plugins {
    id 'jacoco'
}

jacoco {
    toolVersion = "0.8.11"
}

test {
    finalizedBy jacocoTestReport
}

jacocoTestReport {
    reports {
        html.required = true
        xml.required = false
    }
}

覆盖率指标:

行覆盖(Line)、分支覆盖(Branch)、方法覆盖(Method)。报告中高亮未覆盖代码。可设置覆盖率阈值(如 BUNDLE_LINE_COVERED_RATIO = 0.8),低于则构建失败。

最佳实践:

  • CI 流程中应包含 mvn test + mvn jacoco:report
  • 覆盖率目标建议:核心模块 ≥ 80%,非核心 ≥ 60%
  • 避免为追求覆盖率而写无意义断言

第八章:最佳实践与常见问题

8.1 测试命名规范

良好的命名提升可读性、可维护性和故障定位效率。

命名维度推荐规范示例注意事项
测试类名被测类名 + Test 或场景 + TestUserServiceTestLoginSecurityTest避免泛化名称如 Test1;若测试特定行为,可加后缀如 UserServiceIntegrationTest
测试方法名行为驱动命名:should[期望行为]When[触发条件][给定]_[当]_[则](Given_When_Then)shouldReturnUserWhenIdExists()givenValidToken_whenAccessResource_thenGrantAccess()避免 testXxx()(JUnit 3 风格);方法名应完整表达业务意图
显示名称(@DisplayName使用自然语言描述测试目的@DisplayName("用户登录失败时应返回 401 状态码")支持中文、Emoji(如 ”✅ 正常流程” / “⚠️ 异常输入”);仅用于报告展示,不影响执行
参数化测试命名模板使用 {index}, {argumentsWithNames} 提高可读性@ParameterizedTest(name = "输入 {0} 应抛出 IllegalArgumentException")默认模板较晦涩,建议显式定义 name 属性

命名反模式:

  • test1, testMethod → 无意义
  • checkIfUserIsValid → 未说明”在什么条件下”
  • 全大写或下划线分隔(如 TEST_USER_LOGIN)→ 不符合 Java 方法命名惯例

8.2 测试隔离与可重复性

每个测试应独立运行,不受其他测试影响,且结果可稳定复现。

原则实现方式示例注意事项
状态隔离每个测试使用独立实例或重置共享状态@BeforeEach void setUp() { userService = new UserService(); }避免在测试类中使用非 final 静态字段存储状态(如 static List users
外部依赖隔离使用 Mock(Mockito)、内存数据库(H2)、临时文件(@TempDir@Mock Database db; / @TempDir Path tempFolder;真实网络/数据库调用会导致慢、不稳定、不可控
时间隔离注入 Clock 或固定时间源private Clock clock = Clock.fixed(Instant.parse("2023-01-01T00:00:00Z"), ZoneId.of("UTC"));避免直接调用 System.currentTimeMillis()LocalDate.now()
随机性控制固定随机种子Random random = new Random(12345L);若必须用随机数据,应在失败时记录种子以便复现
资源清理使用 @AfterEach / @AfterAlltry-with-resources@AfterEach void tearDown() { if (server != null) server.stop(); }即使测试失败也应释放资源(如文件句柄、端口)
并发安全默认 JUnit 5 测试不并发执行;若启用并行,确保线程安全——并行测试需显式配置(如 junit-platform.propertiesjunit.jupiter.execution.parallel.enabled=true),此时隔离要求更高

破坏隔离的典型行为:

  • 在测试 A 中修改静态变量,测试 B 依赖该变量
  • 多个测试共用同一个数据库连接且未回滚事务
  • 测试方法之间存在执行顺序依赖(如必须先 run testCreate 再 run testDelete

8.3 常见错误排查(如静态方法误用、生命周期混淆等)

以下是 JUnit 5 开发中最常见的错误及其解决方案。

错误现象根本原因排查方法解决方案
@Test 方法未执行方法为 private / static / 有返回值查看 IDE 是否显示绿色 ▶️;检查控制台是否提示 “No tests found”确保方法为 public void methodName()(或 package-private),无参数,无返回值
@BeforeAll 报错 “static method required”@BeforeAll 方法未声明为 static编译或运行时报错:@BeforeAll method 'init' must be static将方法改为 static;或添加 @TestInstance(Lifecycle.PER_CLASS) 并保留非静态
断言失败但异常信息模糊未提供自定义失败消息AssertionError 仅显示 expected vs actual使用带 message 的断言:assertEquals(expected, actual, "用户ID应匹配") 或 Lambda 延迟求值:assertTrue(condition, () -> "当前值: " + value)
参数化测试参数数量/类型不匹配@CsvSource 行数 ≠ 方法参数数运行时报 ParameterResolutionException检查每行 CSV 参数个数;确认类型可自动转换(如 String → int)或提供 ArgumentConverter
扩展未生效忘记添加 @ExtendWith 或依赖缺失自定义逻辑未执行;Mockito 字段为 null检查是否标注 @ExtendWith(MyExtension.class);确认 Maven/Gradle 已引入相关库(如 mockito-junit-jupiter
测试套件(@Suite)不运行缺少 junit-platform-suite 依赖运行套件类时报 ClassNotFoundException添加依赖:Maven: org.junit.platform:junit-platform-suite / Gradle: testImplementation 'org.junit.platform:junit-platform-suite'
动态测试(@TestFactory)不显示为多个测试返回类型错误(如 voidIDE 仅显示一个测试项确保返回 Stream<DynamicTest> 等支持类型;不要用 void 方法
JaCoCo 覆盖率为 0%未正确配置 agentHTML 报告中所有类灰色Maven:确保 jacoco 插件包含 <goal>prepare-agent</goal> / Gradle:确认应用了 id 'jacoco'useJUnitPlatform() 已启用

通用排查技巧:

  1. 最小可复现示例:新建简单测试类验证问题是否仍存在
  2. 查看完整堆栈:IDE 控制台或 surefire-reports/ 中的 .txt 文件
  3. 检查依赖树mvn dependency:tree./gradlew dependencies,确认无版本冲突
  4. 升级到最新版:JUnit 5.10+ 修复了大量历史问题

附录 A:JUnit 5 常用注解速查表

注解名称所在包作用位置功能说明典型用法示例
@Testorg.junit.jupiter.api方法标识一个标准测试方法@Test void testAdd() { ... }
@ParameterizedTestorg.junit.jupiter.params方法标识参数化测试方法@ParameterizedTest @ValueSource(ints={1,2}) void testX(int n) { ... }
@RepeatedTestorg.junit.jupiter.api方法重复执行测试 N 次@RepeatedTest(3) void testFlaky() { ... }
@TestFactoryorg.junit.jupiter.api方法动态生成测试用例@TestFactory Stream<DynamicTest> tests() { ... }
@BeforeEachorg.junit.jupiter.api方法每个测试方法前执行@BeforeEach void setUp() { ... }
@AfterEachorg.junit.jupiter.api方法每个测试方法后执行@AfterEach void tearDown() { ... }
@BeforeAllorg.junit.jupiter.api方法整个测试类开始前执行(需 staticPER_CLASS@BeforeAll static void init() { ... }
@AfterAllorg.junit.jupiter.api方法整个测试类结束后执行@AfterAll static void cleanup() { ... }
@DisplayNameorg.junit.jupiter.api类 / 方法自定义测试显示名称(支持中文/Emoji)@DisplayName("✅ 用户登录成功")
@Disabledorg.junit.jupiter.api类 / 方法禁用测试(跳过执行)@Disabled("待修复") @Test void brokenTest() { ... }
@Tagorg.junit.jupiter.api类 / 方法为测试打标签,用于分组筛选@Tag("integration") class IT { ... }
@ExtendWithorg.junit.jupiter.api类 / 方法注册自定义或内置扩展@ExtendWith(MockitoExtension.class)
@TempDirorg.junit.jupiter.api参数 / 字段注入临时目录 Path(自动清理)@Test void test(@TempDir Path tmp) { ... }
@Suiteorg.junit.platform.suite.api定义测试套件入口@Suite @SelectClasses({A.class}) class MySuite {}
@SelectClassesorg.junit.platform.suite.api类(配合 @Suite指定套件包含的测试类@SelectClasses({LoginTest.class})
@IncludeTags / @ExcludeTagsorg.junit.platform.suite.api类(配合 @Suite按标签包含/排除测试@IncludeTags("fast")
@ValueSource / @CsvSource / @MethodSourceorg.junit.jupiter.params方法(配合 @ParameterizedTest提供参数化测试数据源@ValueSource(strings={"a","b"})

生命周期顺序(单个测试方法):

@BeforeAll → @BeforeEach → @Test / @ParameterizedTest → @AfterEach → @AfterAll

附录 B:JUnit 5 断言方法汇总表(org.junit.jupiter.api.Assertions

断言方法签名(常用重载)用途示例
assertEqualsassertEquals(expected, actual) / assertEquals(expected, actual, delta) / assertEquals(expected, actual, String message)断言两个值相等(使用 equalsassertEquals(4, 2+2); / assertEquals(0.1, result, 0.001);
assertNotEqualsassertNotEquals(unexpected, actual)断言两个值不相等assertNotEquals(null, obj);
assertTrueassertTrue(boolean condition) / assertTrue(condition, Supplier<String> message)断言条件为 trueassertTrue(list.isEmpty(), () -> "size=" + list.size());
assertFalseassertFalse(boolean condition)断言条件为 falseassertFalse(str.isBlank());
assertNullassertNull(Object actual)断言对象为 nullassertNull(cache.get("key"));
assertNotNullassertNotNull(Object actual)断言对象非 nullassertNotNull(result);
assertSameassertSame(expected, actual)断言两个引用指向同一对象(==assertSame(instance, getInstance());
assertNotSameassertNotSame(unexpected, actual)断言两个引用不指向同一对象assertNotSame(new Object(), obj);
assertThrowsassertThrows(Class<T> type, Executable executable)断言执行时抛出指定异常IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> divide(1,0));
assertDoesNotThrowassertDoesNotThrow(Executable executable)断言执行时不抛出异常String s = assertDoesNotThrow(() -> parse(input));
assertTimeoutassertTimeout(Duration timeout, Executable executable)断言在超时内完成(当前线程)assertTimeout(Duration.ofSeconds(1), () -> compute());
assertTimeoutPreemptivelyassertTimeoutPreemptively(Duration timeout, Executable executable)断言在超时内完成(独立线程,可中断)assertTimeoutPreemptively(Duration.ofMillis(100), () -> loopForever());
assertAllassertAll(Stream<Executable> executables) / assertAll(String heading, Executable... execs)组合多个断言,统一报告失败assertAll("user", () -> assertEquals("Tom", u.name), () -> assertTrue(u.active));
failfail(String message) / fail(Throwable cause)主动使测试失败if (unsupported) fail("不支持该配置");

关键说明:

  • 所有断言方法均位于 org.junit.jupiter.api.Assertions,通常静态导入:import static org.junit.jupiter.api.Assertions.*;
  • Executable 是函数式接口:() -> { /* code */ }
  • Supplier<String> 用于延迟构建错误消息,避免无谓字符串拼接开销
  • 浮点数比较必须使用带 deltaassertEquals,避免精度问题