JUnit5 基础用法:第一个测试类与断言

JUnit5(Jupiter)是 Java 生态中事实上的单元测试框架。其核心思想是通过注解声明测试生命周期方法、执行测试用例,并利用断言库验证预期结果。本章将带领你从零创建第一个测试类,掌握 @Test、@BeforeEach、@AfterEach 的正确用法,并熟悉最常用的断言 API。

1. 原理:测试生命周期与注解执行顺序

JUnit5 的测试生命周期由一系列注解驱动。对于每个测试方法(@Test),引擎都会按固定顺序触发钩子方法:

@BeforeAll (static, 类级别,所有测试前执行一次)
  └─ @BeforeEach (每个测试前执行)
       └─ @Test (测试方法体)
       └─ @AfterEach (每个测试后执行)
  └─ @AfterAll (static, 类级别,所有测试后执行一次)

本章聚焦实例级别钩子 @BeforeEach 和 @AfterEach,它们常用于初始化测试资源或清理副作用。

JUnit5 单个测试方法的生命周期(实例级) @BeforeEach @Test @AfterEach 实例级生命周期说明 • 每个 @Test 方法执行前,JUnit 都会创建一个新的测试类实例 • @BeforeEach 在每次 @Test 之前执行,用于初始化(如创建 mock 对象) • @AfterEach 在每次 @Test 之后执行,用于清理(如关闭流或重置状态)

2. 实现流程:编写第一个测试类

  1. 引入 JUnit5 依赖(Maven / Gradle)
  2. 创建待测试类(例如一个简单的计算器)
  3. 编写测试类,使用 @Test 标记测试方法
  4. 使用 @BeforeEach 初始化公共测试数据
  5. 使用 @AfterEach 执行清理(可选)
  6. 添加断言验证方法返回值或异常
  7. 运行测试(IDE 或 Gradle/Maven)

3. 代码示例:Calculator 的单元测试

待测试类 Calculator.java

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
    public int divide(int a, int b) {
        if (b == 0) throw new IllegalArgumentException("除数不能为0");
        return a / b;
    }
}

JUnit5 测试类 CalculatorTest.java

import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {
    private Calculator calculator;

    @BeforeEach
    void setUp() {
        calculator = new Calculator();
        System.out.println("初始化 Calculator 实例");
    }

    @AfterEach
    void tearDown() {
        calculator = null;
        System.out.println("清理 Calculator 实例");
    }

    @Test
    void testAddShouldReturnSum() {
        int result = calculator.add(3, 5);
        assertEquals(8, result, "3 + 5 应等于 8");
    }

    @Test
    void testDivideShouldThrowWhenDivisorIsZero() {
        IllegalArgumentException exception = assertThrows(
                IllegalArgumentException.class,
                () -> calculator.divide(10, 0)
        );
        assertEquals("除数不能为0", exception.getMessage());
    }
}
提示:assertEquals 的第三个参数是可选失败消息,建议编写有意义的描述以快速定位失败原因。

4. 常用断言方法速查表

断言方法 作用 示例
assertEquals(expected, actual) 判断两个对象(或基本类型)相等 assertEquals(5, sum)
assertNotEquals(unexpected, actual) 判断两个对象不相等 assertNotEquals(0, count)
assertTrue(condition) 判断布尔条件为 true assertTrue(list.isEmpty())
assertFalse(condition) 判断布尔条件为 false assertFalse(map.containsKey(null))
assertNull(object) 判断对象为 null assertNull(result)
assertNotNull(object) 判断对象非 null assertNotNull(database)
assertThrows(exceptionClass, executable) 验证代码块抛出了指定异常 assertThrows(IOException.class, () -> file.read())
assertDoesNotThrow(executable) 验证代码块未抛出任何异常 assertDoesNotThrow(() -> service.process())

5. 常见错误与调试

警告:常见陷阱
  • 忘记导入静态断言:必须 import static org.junit.jupiter.api.Assertions.*; 否则无法直接使用 assertEquals 等。
  • @BeforeEach 方法非 void 或参数错误:JUnit5 要求带 @BeforeEach 的方法不能有参数,且返回类型必须为 void(除非结合 TestInstance 等高级用法)。
  • 断言顺序与实际值颠倒:assertEquals(actual, expected) 是常见笔误,正确是 assertEquals(expected, actual)。
  • 测试类必须是 public 或 package-private(默认):JUnit5 允许默认访问权限,但 IDE 有时自动生成 public,两者皆可。
  • @AfterEach 未执行:仅当 @Test 正常结束或抛异常时 @AfterEach 才会执行;若 JVM 崩溃或线程被强行终止则不保证。
调试技巧:使用 System.out.println 或 IDE 内置的测试运行日志(如 IntelliJ “Run” 窗口)观察 @BeforeEach / @AfterEach 的输出顺序,以确认生命周期是否正确。

6. 集成到构建工具(Gradle 示例)

确保 build.gradle 中包含以下依赖:

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
}
test {
    useJUnitPlatform()
}

运行测试命令:

./gradlew test

输出报告位于 build/reports/tests/test/index.html,可直观查看通过/失败趋势。

至此,你已经掌握了 JUnit5 最核心的测试开发流程。下一环节将把这些基础用法与协程测试场景结合,通过 @BeforeEach 初始化协程调度器,利用断言验证挂起函数的返回结果——这正是为 Worker 单元测试打下的扎实地基。