JUnit5 基础用法:第一个测试类与断言
JUnit5(Jupiter)是 Java 生态中事实上的单元测试框架。其核心思想是通过注解声明测试生命周期方法、执行测试用例,并利用断言库验证预期结果。本章将带领你从零创建第一个测试类,掌握 @Test、@BeforeEach、@AfterEach 的正确用法,并熟悉最常用的断言 API。
1. 原理:测试生命周期与注解执行顺序
JUnit5 的测试生命周期由一系列注解驱动。对于每个测试方法(@Test),引擎都会按固定顺序触发钩子方法:
@BeforeAll (static, 类级别,所有测试前执行一次)
└─ @BeforeEach (每个测试前执行)
└─ @Test (测试方法体)
└─ @AfterEach (每个测试后执行)
└─ @AfterAll (static, 类级别,所有测试后执行一次)
本章聚焦实例级别钩子 @BeforeEach 和 @AfterEach,它们常用于初始化测试资源或清理副作用。
2. 实现流程:编写第一个测试类
- 引入 JUnit5 依赖(Maven / Gradle)
- 创建待测试类(例如一个简单的计算器)
- 编写测试类,使用
@Test标记测试方法 - 使用 @BeforeEach 初始化公共测试数据
- 使用 @AfterEach 执行清理(可选)
- 添加断言验证方法返回值或异常
- 运行测试(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 单元测试打下的扎实地基。