乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 介绍与核心概念

    • Maven是什么
    • 约定优于配置
  • 第2章 安装与配置

    • 安装与验证
    • settings.xml
    • 本地仓库与镜像
  • 第3章 POM与项目坐标

    • POM
    • GAV坐标
    • packaging
  • 第4章 标准目录布局

    • 标准目录布局
  • 第5章 依赖机制

    • dependencies
    • scope
    • 依赖传递
    • 依赖冲突与调解
    • exclusions
    • optional
    • dependencyManagement
  • 第6章 仓库

    • 仓库体系
    • 本地仓库
    • 远程仓库与镜像
    • 私服
  • 第7章 构建生命周期

    • 生命周期概述
    • clean 生命周期
    • default 生命周期
    • site 生命周期
    • 生命周期与插件绑定
  • 第8章 插件

    • 插件概述
    • maven-compiler-plugin
    • maven-surefire-plugin
    • maven-war-plugin
  • 第9章 继承与聚合

    • parent继承
    • 聚合
    • BOM
    • properties
  • 第10章 属性与资源过滤

    • 资源过滤
    • Profile
  • 第11章 常用命令

    • mvn compile
    • mvn test
    • mvn package
    • mvn clean
    • mvn install
    • mvn dependency:tree
  • 第12章 常见问题与最佳实践

    • 依赖冲突排查
    • 最佳实践

mvn test

本章承接 mvn compile,进入 default 生命周期的测试阶段。理解 mvn test 的作用和测试执行机制,是掌握 Maven 质量保证体系的关键——自动化测试是持续集成的基础。


核心机制

test 阶段使用合适的单元测试框架运行项目的单元测试。这些测试不应要求代码已被打包或部署。

这里有两个重要限定:

  1. 单元测试 —— mvn test 默认运行的是单元测试,不是集成测试(集成测试通常在 integration-test 阶段执行)
  2. 不依赖打包 —— 测试在 package 之前执行,此时还没有 JAR/WAR 产物,测试只能依赖 target/classes 和 target/test-classes 中的类文件

test 阶段在生命周期中的位置

default 生命周期的相关阶段链:

... → compile → test-compile → test → package → ...

执行 mvn test 时,Maven 会自动依次执行:

  1. validate —— 验证项目
  2. compile —— 编译主源码
  3. test-compile —— 编译测试源码
  4. test —— 运行测试

谁在做真正的测试工作?

test 阶段默认绑定了 maven-surefire-plugin 的 test 目标。Surefire 插件负责:

  • 扫描 target/test-classes 中的测试类
  • 启动 JVM 测试进程
  • 调用 JUnit 或 TestNG 框架执行测试方法
  • 收集测试结果并生成报告
mvn test
    → 触发 test 阶段
        → 调用 maven-surefire-plugin:test
            → 扫描测试类
                → 启动 JUnit/TestNG
                    → 执行 @Test 方法
                        → 生成测试报告

测试类识别规则

Surefire 插件默认识别以下模式的类作为测试类:

  • **/Test*.java —— 以 Test 开头的类
  • **/*Test.java —— 以 Test 结尾的类
  • **/*Tests.java —— 以 Tests 结尾的类
  • **/*TestCase.java —— 以 TestCase 结尾的类

注意:测试类必须是 public 的,且包含至少一个 @Test 注解的方法(JUnit 5)或 @Test 注解的方法(JUnit 4)。

生活类比:汽车出厂质检

想象一家汽车工厂(Maven 构建流程):

  • compile 阶段是组装车间,把零件(源码)组装成整车(.class 文件)
  • test 阶段是质检车间,对每一辆下线的汽车进行安全检测:刹车测试、灯光测试、引擎测试
  • 如果任何一项测试失败(如刹车失灵),整车被判定为不合格(BUILD FAILURE),不会进入后续的喷漆打包(package)环节
  • 质检报告(surefire-reports/)详细记录了每辆车的每项检测结果,供工程师追溯

测试的价值在于:在代码进入打包和部署阶段之前,尽早发现问题。


图示

上图展示了 mvn test 的完整执行流程:先自动完成编译和测试编译,然后 Surefire 插件扫描并执行测试类,根据结果判定构建成功或失败,同时生成 XML 和文本格式的测试报告。


完整示例

场景

飞翔科技的 employee-system 项目中,后端小崔为 EmployeeService 编写了单元测试。CTO 大翔要求:任何代码提交前必须通过全部测试。

操作前:项目状态

src/test/java/com/feixiang/service/EmployeeServiceTest.java:

package com.feixiang.service;

import com.feixiang.model.Employee;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class EmployeeServiceTest {
    
    private EmployeeService service;
    
    @BeforeEach
    void setUp() {
        service = new EmployeeService();
    }
    
    @Test
    void shouldAddEmployee() {
        Employee emp = new Employee(1L, "张三", "engineer");
        service.addEmployee(emp);
        
        assertEquals(1, service.getAllEmployees().size());
        assertEquals("张三", service.getAllEmployees().get(0).getName());
    }
    
    @Test
    void shouldFindEmployeeById() {
        Employee emp1 = new Employee(1L, "张三", "engineer");
        Employee emp2 = new Employee(2L, "李四", "manager");
        service.addEmployee(emp1);
        service.addEmployee(emp2);
        
        Employee found = service.findById(2L);
        
        assertNotNull(found);
        assertEquals("李四", found.getName());
    }
    
    @Test
    void shouldReturnNullWhenNotFound() {
        Employee found = service.findById(999L);
        assertNull(found);
    }
}

pom.xml 中的测试依赖:

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

操作步骤

步骤一:执行测试

mvn test

步骤二:观察输出(全部通过)

[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------< com.feixiang:employee-system >------------------
[INFO] Building employee-system 1.0.0
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- compiler:3.11.0:compile (default-compile) @ employee-system ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ employee-system ---
[INFO] Compiling 1 source file to C:\Users\xiaocui\workspace\employee-system\target\test-classes
[INFO] 
[INFO] --- surefire:3.1.2:test (default-test) @ employee-system ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO] 
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.feixiang.service.EmployeeServiceTest
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.523 s
[INFO] 
[INFO] Results :
[INFO] 
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

关键输出解读:

  • testCompile —— 先编译测试源码到 target/test-classes
  • surefire:3.1.2:test —— Surefire 插件执行测试
  • Tests run: 3, Failures: 0 —— 3 个测试全部通过
  • BUILD SUCCESS —— 构建成功

测试报告位置

测试执行后,以下目录生成报告:

target/
├── test-classes/                    ← 编译后的测试类
│   └── com/feixiang/service/EmployeeServiceTest.class
└── surefire-reports/                ← 测试报告
    ├── com.feixiang.service.EmployeeServiceTest.txt
    └── TEST-com.feixiang.service.EmployeeServiceTest.xml

文本报告(.txt)内容示例:

-------------------------------------------------------------------------------
Test set: com.feixiang.service.EmployeeServiceTest
-------------------------------------------------------------------------------
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.523 s

XML 报告(.xml)是机器可读的格式,被 Jenkins、GitHub Actions 等 CI 工具解析用于展示测试趋势图。

测试失败的处理

假设小崔不小心改坏了 findById 方法:

public Employee findById(Long id) {
    return employees.stream()
        .filter(e -> e.getId() == id)  // 错误:Long 用 == 比较
        .findFirst()
        .orElse(null);
}

执行 mvn test 后的输出:

[INFO] Running com.feixiang.service.EmployeeServiceTest
[ERROR] Tests run: 3, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.612 s
[ERROR] shouldFindEmployeeById(com.feixiang.service.EmployeeServiceTest)  Time elapsed: 0.023 s  <<< FAILURE
org.opentest4j.AssertionFailedError: expected: not <null>
    at com.feixiang.service.EmployeeServiceTest.shouldFindEmployeeById(EmployeeServiceTest.java:35)
[INFO] 
[ERROR] Failures: 
[ERROR]   EmployeeServiceTest.shouldFindEmployeeById:35 expected: not <null>
[ERROR] Tests run: 3, Failures: 1, Errors: 0, Skipped: 0
[ERROR] 
[ERROR] There are test failures.
[ERROR] 
[ERROR] Please refer to C:\Users\xiaocui\workspace\employee-system\target\surefire-reports
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------

处理步骤:

  1. 查看失败信息:shouldFindEmployeeById 在 EmployeeServiceTest.java:35 失败
  2. 定位原因:findById 方法中 Long 对象用了 == 比较,应该用 .equals()
  3. 修复代码后重新执行 mvn test
  4. 全部通过后继续后续构建

易错点与常见问题

误区一:测试失败不影响打包

错误认知:"我执行 mvn package,虽然测试失败了,但 JAR 包还是生成了。"

纠正:mvn package 会依次执行 compile → test-compile → test → package。如果 test 阶段失败,Maven 默认会停止构建,不会执行 package。如果你看到 JAR 生成了,说明:

  • 你执行的是 mvn package -DskipTests(跳过测试)
  • 或者你执行的是 mvn compile package(跳过了 test 阶段,不推荐)

正确做法:修复测试后再打包,或仅在明确知道风险的情况下跳过测试。

误区二:mvn test 只运行修改过的测试

错误认知:"我只改了 EmployeeService.java,mvn test 应该只运行相关的测试类。"

纠正:mvn test 默认会运行所有测试类。Surefire 插件没有内置"只运行受影响的测试"的能力。如果需要选择性运行,可以使用:

# 只运行指定测试类
mvn test -Dtest=EmployeeServiceTest

# 只运行指定方法
mvn test -Dtest=EmployeeServiceTest#shouldAddEmployee

# 运行匹配通配符的测试类
mvn test -Dtest="*ServiceTest"

误区三:测试报告只有控制台输出

错误认知:"测试结果显示在控制台,我想保存下来只能复制粘贴。"

纠正:Surefire 插件自动生成两种格式的报告:

  • target/surefire-reports/*.txt —— 人类可读的文本摘要
  • target/surefire-reports/*.xml —— 机器可读的 Surefire XML 格式

CI 工具(Jenkins、GitLab CI、GitHub Actions)会自动解析 XML 报告,在界面上展示测试通过/失败的趋势图。

误区四:@Test 方法必须是 public

背景:JUnit 4 要求测试方法必须是 public,但 JUnit 5(Jupiter)放宽了这一限制。

纠正:

  • JUnit 4:@Test 方法必须是 public,类也必须是 public
  • JUnit 5:@Test 方法可以是 package-private(默认访问级别),类也可以是 package-private

飞翔科技的项目统一使用 JUnit 5,因此小崔写的测试类不需要 public 修饰符。


小结

mvn test 是 Maven 自动化测试的核心命令,它通过 maven-surefire-plugin 编译并运行 src/test/java 中的单元测试。核心要点:

  • mvn test 自动执行 compile 和 test-compile,然后运行测试
  • 测试失败默认导致 BUILD FAILURE,阻止后续打包
  • 测试报告生成在 target/surefire-reports/,包含 .txt 和 .xml 两种格式
  • 可用 -Dtest=类名 选择性运行特定测试
  • JUnit 5 的测试类和测试方法可以是 package-private

本章与全局的关系:本章讲解了测试执行。下一章 mvn package 将展示如何在测试通过后,将编译产物打包为可发布的 JAR 文件。

上一页
mvn compile
下一页
mvn package