下载工作台
Java 编程实战

Maven、JUnit 5 与单元测试

试读上半部分 · 解锁后可读全文

第 12 章 · Maven、JUnit 5 与单元测试

本章目标:理解 Maven 项目结构与生命周期;使用 JUnit 5 编写单元测试与 @Test;掌握断言、参数化测试入门;了解 Mockito 模拟依赖;为 toolkit-demo 建立可回归的测试基线。

学时建议:4~5 小时(含 1.5 小时跟练)

前置ch01 JDK 环境;ch05 类与包;ch11 JDBC(可选,用于集成测试示例)。

包结构说明:ch01~ch11 代码在 demo.modeldemo.io 等子包;本章测试demo.toolkit(与 ch01 的 package demo 同属 toolkit-demo,groupIdcom.example.learn)。ch17 毕业项目demo.report,请在同一 Maven 工程内按包划分。

12.1 为什么需要构建与测试

问题工具
依赖管理混乱Maven / Gradle
改代码怕回归单元测试
协作目录不统一标准 src/main / src/test
假对象替代数据库Mockito
开发 → 编写测试 → mvn test 绿灯 → 提交
         ↓ 红灯
       修复后再测

12.2 Maven 项目结构

toolkit-demo/
├── pom.xml                 # 项目对象模型
├── src/
│   ├── main/
│   │   ├── java/           # 主代码
│   │   │   └── com/example/toolkit/
│   │   └── resources/      # 配置、静态资源
│   └── test/
│       ├── java/           # 测试代码
│       └── resources/
└── target/                 # 编译输出(可删)

坐标pom.xml 核心):

<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>toolkit-demo</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
</project>
与 ch01 的 toolkit-demo 保持一致使用 JDK 21;若本机只有 JDK 17,把两处 21 改为 17 即可,本章代码兼容 17。
元素含义
groupId组织/公司
artifactId项目名
version版本号

12.3 Maven 生命周期

Maven 有三套生命周期,最常用 default

validate → compile → test → package → verify → install → deploy
阶段作用
compile编译主代码
test-compile编译测试代码
test运行单元测试
package打 jar/war
install安装到本地仓库
clean清理 target(另一生命周期)

常用命令

cd ~/learn-java/toolkit-demo
mvn clean compile          # 清理并编译
mvn test                   # 运行测试
mvn package -DskipTests    # 打包跳过测试(慎用)
mvn dependency:tree        # 查看依赖树

12.4 依赖管理

<dependencies>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.10.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.2.5</version>
        </plugin>
    </plugins>
</build>
scope说明
compile默认,主代码与测试均可见
test仅测试代码
provided编译需要,运行时由容器提供

12.5 JUnit 5 第一个测试

被测类:

package demo.toolkit;

public class DiscountCalculator {
    public double discount(double price, double rate) {
        if (price < 0 || rate < 0 || rate > 1) {
            throw new IllegalArgumentException("非法参数");
        }
        return price * (1 - rate);
    }
}

测试类:

package demo.toolkit;

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

class DiscountCalculatorTest {

    @Test
    void discount_applies_rate() {
        DiscountCalculator calc = new DiscountCalculator();
        assertEquals(80.0, calc.discount(100.0, 0.2));
    }

    @Test
    void discount_rejects_negative_price() {
        DiscountCalculator calc = new DiscountCalculator();
        assertThrows(IllegalArgumentException.class,
            () -> calc.discount(-1, 0.1));
    }
}

运行:mvn test,Surefire 自动发现 *Test 类。


12.6 常用断言

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

assertEquals(expected, actual);
assertNotEquals(unexpected, actual);
assertTrue(condition);
assertFalse(condition);
assertNull(value);
assertNotNull(value);
assertArrayEquals(expectedArray, actualArray);

// 浮点近似
assertEquals(0.1 + 0.2, 0.3, 0.0001);

// 异常
Exception ex = assertThrows(IllegalStateException.class, () -> {
    throw new IllegalStateException("msg");
});
assertEquals("msg", ex.getMessage());
断言用途
assertEquals值相等
assertThrows期望抛异常
assertTimeout性能/超时

12.7 测试生命周期注解

import org.junit.jupiter.api.*;

class LifecycleDemo {

    @BeforeAll
    static void initAll() {
        System.out.println("全部测试开始前一次");
    }

    @BeforeEach
    void init() {
        System.out.println("每个测试前");
    }

    @Test
    void testOne() { }

    @Test
    void testTwo() { }

    @AfterEach
    void tearDown() {
        System.out.println("每个测试后");

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。