Skip to content

单元测试入门(JUnit)

概述

单元测试(Unit Test)是针对代码中最小可测单元(通常是一个方法)的自动化测试,用于验证行为是否符合预期。写好单元测试能及早发现错误、方便重构,并作为「活文档」帮助他人理解用法。JUnit 是 Java 生态中最主流的单元测试框架,目前广泛使用的是 JUnit 5(Jupiter),本文以 JUnit 5 为例介绍基本用法。

为什么写单元测试

单元测试可以:在修改代码后快速验证是否破坏原有逻辑;促使你写出更易测试、职责单一的方法;在多人协作时减少回归问题。


添加 JUnit 依赖

Maven 为例,在 pom.xml 中声明 JUnit 5 依赖(通常放在 test 作用域):

xml
<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-apijunit-jupiter-params(参数化测试需要时可再加)。Gradle 用户可在 build.gradle 中写:

groovy
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2'

测试代码放在 Maven 约定src/test/java 下,包名与主代码对应(如 com.example.App 对应 com.example.AppTest)。详见 常用构建工具(Maven / Gradle 入门)


基本用法:@Test 与断言

第一个测试方法

@Test 标记一个方法为测试方法;方法通常为 void、无参,方法名建议清晰描述「测什么、在什么条件下、期望什么」。

java
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);
    }
}

被测类示例:

java
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...)执行多组断言,全部失败信息一起输出

示例:断言异常与多条件:

java
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:工具方法

被测类:一个简单的字符串工具。

java
public class StringUtils {
    public static boolean isBlank(CharSequence cs) {
        if (cs == null) return true;
        return cs.chars().allMatch(Character::isWhitespace);
    }
}

测试类:

java
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)。

java
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)),且多用于昂贵资源的一次性初始化或清理。


注意事项与最佳实践

  1. 测试应独立、可重复:不依赖执行顺序,不依赖外部环境(如固定端口、本地文件路径),避免共享可变状态。
  2. 命名清晰:测试方法名能表达「被测行为 + 条件 + 期望」,例如 add_shouldReturnSum_whenTwoPositiveNumbers;也可用 @DisplayName 写中文或更长描述。
  3. 一个测试聚焦一个行为:一个 @Test 尽量只验证一个逻辑点,失败时便于定位。
  4. 优先测公共行为:优先覆盖对外 API 和核心逻辑,不必追求 100% 行覆盖。
  5. 避免在测试里写复杂逻辑:测试代码要简单直观,否则测试本身容易出错。

提示

若被测方法依赖数据库、网络等,可先学习「测试替身」(Mock),使用 Mockito 等框架模拟依赖,再在进阶文档或项目中实践。


相关链接

基于 VitePress 构建