单元测试入门(JUnit)
概述
单元测试(Unit Test)是针对代码中最小可测单元(通常是一个方法)的自动化测试,用于验证行为是否符合预期。写好单元测试能及早发现错误、方便重构,并作为「活文档」帮助他人理解用法。JUnit 是 Java 生态中最主流的单元测试框架,目前广泛使用的是 JUnit 5(Jupiter),本文以 JUnit 5 为例介绍基本用法。
为什么写单元测试
单元测试可以:在修改代码后快速验证是否破坏原有逻辑;促使你写出更易测试、职责单一的方法;在多人协作时减少回归问题。
添加 JUnit 依赖
以 Maven 为例,在 pom.xml 中声明 JUnit 5 依赖(通常放在 test 作用域):
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
</dependencies>junit-jupiter 已包含引擎与常用断言,无需再单独引入 junit-jupiter-api 与 junit-jupiter-params(参数化测试需要时可再加)。Gradle 用户可在 build.gradle 中写:
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2'测试代码放在 Maven 约定的 src/test/java 下,包名与主代码对应(如 com.example.App 对应 com.example.AppTest)。详见 常用构建工具(Maven / Gradle 入门)。
基本用法:@Test 与断言
第一个测试方法
用 @Test 标记一个方法为测试方法;方法通常为 void、无参,方法名建议清晰描述「测什么、在什么条件下、期望什么」。
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CalculatorTest {
@Test
void add_shouldReturnSum_whenTwoPositiveNumbers() {
Calculator calc = new Calculator();
int result = calc.add(2, 3);
assertEquals(5, result);
}
}被测类示例:
public class Calculator {
public int add(int a, int b) {
return a + b;
}
}运行测试:在 IDE 中点击方法或类旁的运行按钮,或使用 Maven 命令 mvn test、Gradle 的 ./gradlew test。
常用断言
JUnit 5 的断言集中在 Assertions 中,常用静态方法如下:
| 方法 | 说明 |
|---|---|
assertEquals(expected, actual) | 相等(含重载:带 delta 的浮点比较、消息等) |
assertNotEquals(unexpected, actual) | 不相等 |
assertTrue(condition) / assertFalse(condition) | 布尔条件 |
assertNull(actual) / assertNotNull(actual) | 是否为 null |
assertThrows(异常类型, executable) | 期望抛出指定异常 |
assertAll(executables...) | 执行多组断言,全部失败信息一起输出 |
示例:断言异常与多条件:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class StringUtilsTest {
@Test
void parsePositive_shouldThrow_whenNegative() {
IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
() -> StringUtils.parsePositive("-1"));
assertTrue(e.getMessage().contains("positive"));
}
@Test
void userFields_shouldMatch() {
User u = new User("alice", 20);
assertAll(
() -> assertEquals("alice", u.getName()),
() -> assertEquals(20, u.getAge())
);
}
}提示
使用 assertAll 可以在一次测试中执行多条断言,即使前面失败也会继续执行后面的断言,方便一次看到所有不符合预期的点。
示例:从简单到实用
示例 1:工具方法
被测类:一个简单的字符串工具。
public class StringUtils {
public static boolean isBlank(CharSequence cs) {
if (cs == null) return true;
return cs.chars().allMatch(Character::isWhitespace);
}
}测试类:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class StringUtilsTest {
@Test
void isBlank_shouldBeTrue_whenNull() {
assertTrue(StringUtils.isBlank(null));
}
@Test
void isBlank_shouldBeTrue_whenEmptyOrWhitespace() {
assertTrue(StringUtils.isBlank(""));
assertTrue(StringUtils.isBlank(" "));
}
@Test
void isBlank_shouldBeFalse_whenHasContent() {
assertFalse(StringUtils.isBlank("hello"));
assertFalse(StringUtils.isBlank(" x "));
}
}示例 2:带生命周期的测试
若多个测试需要相同的准备与清理(如初始化对象、关闭资源),可使用 @BeforeEach、@AfterEach(每个测试方法前后执行)或 @BeforeAll、@AfterAll(整个测试类执行一次,方法需 static)。
import org.junit.jupiter.api.*;
class UserServiceTest {
private UserService userService;
@BeforeEach
void setUp() {
userService = new UserService();
}
@Test
void findByName_shouldReturnUser_whenExists() {
User u = userService.findByName("alice");
assertNotNull(u);
assertEquals("alice", u.getName());
}
@Test
void findByName_shouldReturnNull_whenNotExists() {
User u = userService.findByName("nobody");
assertNull(u);
}
}生命周期与常用注解小结
| 注解 | 作用 |
|---|---|
@Test | 标记测试方法 |
@BeforeEach | 每个 @Test 执行前运行 |
@AfterEach | 每个 @Test 执行后运行 |
@BeforeAll | 当前类所有测试执行前运行一次(方法需 static) |
@AfterAll | 当前类所有测试执行后运行一次(方法需 static) |
@DisplayName("描述") | 为测试类或方法设置可读名称,便于报告阅读 |
注意
@BeforeAll / @AfterAll 对应的方法必须是 static void(除非使用 @TestInstance(Lifecycle.PER_CLASS)),且多用于昂贵资源的一次性初始化或清理。
注意事项与最佳实践
- 测试应独立、可重复:不依赖执行顺序,不依赖外部环境(如固定端口、本地文件路径),避免共享可变状态。
- 命名清晰:测试方法名能表达「被测行为 + 条件 + 期望」,例如
add_shouldReturnSum_whenTwoPositiveNumbers;也可用@DisplayName写中文或更长描述。 - 一个测试聚焦一个行为:一个
@Test尽量只验证一个逻辑点,失败时便于定位。 - 优先测公共行为:优先覆盖对外 API 和核心逻辑,不必追求 100% 行覆盖。
- 避免在测试里写复杂逻辑:测试代码要简单直观,否则测试本身容易出错。
提示
若被测方法依赖数据库、网络等,可先学习「测试替身」(Mock),使用 Mockito 等框架模拟依赖,再在进阶文档或项目中实践。
相关链接
- 编码规范 — 保持测试代码风格一致
- 常用构建工具(Maven / Gradle 入门) — 项目结构与
mvn test/ Gradle 测试 - 注解(Annotation) — 理解
@Test、@BeforeEach等注解机制 - JUnit 5 用户指南(英文)
- Oracle Java 文档