mvn test
本章承接
mvn compile,进入 default 生命周期的测试阶段。理解mvn test的作用和测试执行机制,是掌握 Maven 质量保证体系的关键——自动化测试是持续集成的基础。
核心机制
test 阶段使用合适的单元测试框架运行项目的单元测试。这些测试不应要求代码已被打包或部署。
这里有两个重要限定:
- 单元测试 ——
mvn test默认运行的是单元测试,不是集成测试(集成测试通常在integration-test阶段执行) - 不依赖打包 —— 测试在
package之前执行,此时还没有 JAR/WAR 产物,测试只能依赖target/classes和target/test-classes中的类文件
test 阶段在生命周期中的位置
default 生命周期的相关阶段链:
... → compile → test-compile → test → package → ...
执行 mvn test 时,Maven 会自动依次执行:
validate—— 验证项目compile—— 编译主源码test-compile—— 编译测试源码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-classessurefire: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] ------------------------------------------------------------------------
处理步骤:
- 查看失败信息:
shouldFindEmployeeById在EmployeeServiceTest.java:35失败 - 定位原因:
findById方法中Long对象用了==比较,应该用.equals() - 修复代码后重新执行
mvn test - 全部通过后继续后续构建
易错点与常见问题
误区一:测试失败不影响打包
错误认知:"我执行 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 文件。