第25课:认识 JUnit 运行时

覆盖知识点:KP-100 ~ KP-104

什么是 JUnit 运行时

JUnit 是 Java 生态中最主流的单元测试运行时(Runtime)。所谓”运行时”,是指 JUnit 提供了一个轻量级的执行环境,专门负责:

  • 发现带有 @Test 注解的方法
  • 为每个测试方法创建独立的实例
  • 执行测试并收集结果
  • 生成测试报告
flowchart LR
    subgraph ”JUnit Runtime”
        direction TB
        J[JUnit Engine]
        T1[Test Method 1]
        T2[Test Method 2]
        T3[Test Method N]
        J --> T1 & T2 & T3
    end

    subgraph ”加载的库”
        M[Mockito]
        JU[JUnit]
    end

    subgraph ”不加载的内容”
        DB[(Database)]
        WEB[Web Container]
        Ioc[IoC 容器]
    end

    JUnit Runtime --> M
    JUnit Runtime --> JU
    JUnit Runtime -.->|不加载| DB
    JUnit Runtime -.->|不加载| WEB
    JUnit Runtime -.->|不加载| Ioc

轻量化是关键

JUnit 运行时只加载测试相关的库(如 JUnit 本身、Mockito),不会启动 Web 容器、数据库连接或 IoC 容器。这使得单元测试的执行速度极快——通常只需几毫秒到几百毫秒。

JUnit 运行时 vs Spring Boot 运行时

维度JUnit 运行时Spring Boot 运行时
启动速度毫秒级秒级到分钟级
加载范围仅测试库整个应用上下文
依赖注入完整的 DI 容器
Web 容器不启动内嵌 Tomcat/Jetty
数据库无连接完整数据源
适用场景纯单元测试集成测试 / E2E 测试
flowchart TD
    subgraph ”错误做法”
        A[每个测试都启动 Spring Boot] --> B[测试慢 → 不想跑 → 测试被废弃]
    end

    subgraph ”正确做法”
        C[JUnit 运行时跑纯单元测试] --> D[毫秒级反馈]
        D --> E[频繁运行 → 即时发现问题]
    end

性能陷阱

不要在单元测试中随意引入 @SpringBootTest。启动整个 Spring 上下文会使测试变慢 100~1000 倍,导致开发者不愿意频繁运行测试,最终使测试失去”守护进程”的价值。

@Test 注解的本质

@Test 注解是 JUnit 的核心入口标记。它告诉 JUnit 引擎:“这个方法是一个测试方法”。

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
 
class CalculatorTest {
 
    @Test
    void shouldReturnSumWhenAddingTwoNumbers() {
        // Given
        Calculator calculator = new Calculator();
 
        // When
        int result = calculator.add(3, 5);
 
        // Then
        assertEquals(8, result);
    }
}

@Test 注解的关键特性:

  • 无需继承或实现接口——JUnit 5 中使用 @Test 是纯粹的自上而下标记
  • 每个测试方法独立运行——JUnit 会为每个 @Test 方法创建新的测试类实例,避免状态污染
  • 方法名无约束——你可以用任何方法名,推荐使用表达意图的名字(如 shouldReturnSumWhenAddingTwoNumbers

运行测试

Gradle

# 运行所有测试
./gradlew test
 
# 运行特定测试类
./gradlew test --tests "*CalculatorTest"
 
# 运行特定方法
./gradlew test --tests "*CalculatorTest.shouldReturnSumWhenAddingTwoNumbers"

Maven

# 运行所有测试
mvn test
 
# 运行特定测试类
mvn test -Dtest=CalculatorTest
 
# 运行特定方法
mvn test -Dtest=CalculatorTest#shouldReturnSumWhenAddingTwoNumbers

测试报告

运行测试后,Gradle 和 Maven 都会生成 HTML 格式的测试报告:

构建工具报告路径
Gradlebuild/reports/tests/index.html
Maventarget/surefire-reports/*.xml + target/site/index.html

报告内容包含:

  • 测试结果概览:通过 / 失败 / 跳过的数量
  • 失败详情:断言失败的堆栈跟踪
  • 执行时间:每个测试的运行时长

报告的价值

测试报告不仅用于 CI/CD 流水线,日常开发中也可以通过报告快速定位失败的测试,了解测试覆盖情况。养成”跑完测试看报告”的习惯。

JUnit 的生命周期方法

除了 @Test,JUnit 5 还提供了一些生命周期钩子:

注解执行时机用途
@BeforeEach每个 @Test 方法之前初始化测试数据、创建 Mock
@AfterEach每个 @Test 方法之后清理资源、重置状态
@BeforeAll所有测试之前(静态方法)建立数据库连接、加载全局配置
@AfterAll所有测试之后(静态方法)关闭资源
import org.junit.jupiter.api.*;
 
class UserServiceTest {
 
    private UserService userService;
 
    @BeforeEach
    void setUp() {
        userService = new UserService();
        System.out.println("每个测试前执行:初始化 UserService");
    }
 
    @AfterEach
    void tearDown() {
        System.out.println("每个测试后执行:清理资源");
    }
 
    @Test
    void shouldCreateUser() {
        // 测试创建用户
    }
 
    @Test
    void shouldDeleteUser() {
        // 测试删除用户
    }
}

方法可见性

JUnit 5 中 @Test@BeforeEach@AfterEach 等方法可以是 package-private(默认可见性),不必是 public。这减少了无关的修饰符噪音。

总结

  • JUnit 是单元测试的运行时环境,轻量、快速
  • 与 Spring Boot 运行时不同,JUnit 只加载测试相关库,不启动应用容器
  • @Test 注解标记测试方法,JUnit 引擎负责发现和执行
  • Gradle/Maven 通过 test 命令运行测试并生成 HTML 报告
  • 利用 @BeforeEach / @AfterEach 等生命周期钩子管理测试前后逻辑
flowchart LR
    A[编写 @Test 方法] --> B[Gradle/Maven test 命令]
    B --> C[JUnit 引擎发现测试]
    C --> D[执行 @BeforeEach]
    D --> E[执行 @Test 方法体]
    E --> F[执行断言]
    F --> G[执行 @AfterEach]
    G --> H[生成测试报告]

    F -->|成功| I[绿色 ✓]
    F -->|失败| J[红色 ✗]

“JUnit 运行时是单元测试的基石——理解它,才能真正理解测试的执行过程。” — Chuck 老师

下一课:Mock 与单元测试的方法论 | 测试术语表 | 深入断言