maven-surefire-plugin
本章承接
maven-compiler-plugin,讲解构建流程的下一环:测试。maven-surefire-plugin是 Maven 的测试执行引擎,负责在编译完成后自动运行单元测试。理解它的执行时机和报告机制,是搭建 CI/CD 流水线的必备知识。
核心机制
理解 Surefire 需要把握两个关键点:
- test phase:Surefire 绑定在
test生命周期阶段,不是compile,也不是package - unit tests:Surefire 主要执行单元测试(通常指 JUnit、TestNG 等框架编写的测试),集成测试由
maven-failsafe-plugin负责
Surefire 在生命周期中的位置
Surefire 插件绑定在 test 阶段,而 test 阶段位于生命周期中的这个位置:
validate → initialize → compile → test-compile → test → package → verify → install → deploy
↑
Surefire 在这里执行
这意味着:
- 必须先完成
compile(主代码编译)和test-compile(测试代码编译) - 如果编译失败,Surefire 不会执行
- 如果测试失败,默认情况下
package及后续阶段不会执行(构建中断)
测试执行流程
Surefire 的执行流程可以概括为:
- 扫描:在
target/test-classes中扫描符合命名规范的测试类 - 加载:通过反射加载测试类,识别
@Test注解的方法 - 执行:调用 JUnit/TestNG 框架运行测试方法
- 报告:生成 XML 和 HTML 格式的测试报告到
target/surefire-reports
测试类命名规范
Surefire 默认只识别符合以下模式的类:
| 模式 | 示例 |
|---|---|
*Test.java | EmployeeServiceTest.java |
Test*.java | TestEmployeeService.java(不推荐) |
*TestCase.java | EmployeeServiceTestCase.java |
注意:Abstract*Test.java 默认会被跳过,因为 Surefire 认为抽象类不是可执行的测试。
生活类比:工厂质检员
想象 Maven 构建是一家食品工厂的生产线:
compile阶段 = 原料加工,把面粉做成饼干胚test阶段 = 质检员(Surefire)抽检饼干package阶段 = 把通过质检的饼干装袋
Surefire 这位质检员的工作规则是:
- 只检查本批次的饼干(本次编译生成的测试类)
- 如果发现发霉的饼干(测试失败),立即拉响警报,停止整条生产线(构建失败)
- 每天下班前写一份质检报告(
surefire-reports),记录抽检数量和不合格品数量
图示
上图展示了 Surefire 在生命周期中的关键位置:编译完成后、打包之前。测试通过,构建继续;测试失败,构建中断。这个设计确保了"有问题的代码不会被打包",是 Maven 保障代码质量的核心机制。
完整示例
场景
飞翔科技的 employee-system 项目新增了员工信息校验逻辑。后端小崔写了一个 EmployeeValidator 类,并配套编写了 JUnit 5 单元测试。架构师白歌要求:任何测试失败的代码都不能进入打包阶段。
操作前:项目状态
pom.xml 已声明 JUnit 5 依赖:
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
</dependencies>
src/test/java/com/feixiang/employee/EmployeeValidatorTest.java:
package com.feixiang.employee;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class EmployeeValidatorTest {
@Test
void shouldRejectEmptyName() {
EmployeeValidator validator = new EmployeeValidator();
assertFalse(validator.isValid(new Employee("", 5000)));
}
@Test
void shouldAcceptValidName() {
EmployeeValidator validator = new EmployeeValidator();
assertTrue(validator.isValid(new Employee("张三", 5000)));
}
}
操作步骤:执行测试
小崔运行完整构建:
$ mvn clean test
操作结果:测试通过
[INFO] --- maven-surefire-plugin:3.0.0-M9:test (default-test) @ employee-system ---
[INFO] Running com.feixiang.employee.EmployeeValidatorTest
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.045 s
[INFO]
[INFO] Results :
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
查看生成的测试报告:
$ ls target/surefire-reports/
com.feixiang.employee.EmployeeValidatorTest.txt
TEST-com.feixiang.employee.EmployeeValidatorTest.xml
XML 报告片段(可被 Jenkins 等 CI 工具解析):
<testsuite name="com.feixiang.employee.EmployeeValidatorTest" tests="2" failures="0" errors="0" skipped="0" time="0.045">
<testcase name="shouldRejectEmptyName" classname="com.feixiang.employee.EmployeeValidatorTest" time="0.021"/>
<testcase name="shouldAcceptValidName" classname="com.feixiang.employee.EmployeeValidatorTest" time="0.018"/>
</testsuite>
反例:测试失败阻断构建
小崔不小心改坏了校验逻辑,导致 shouldRejectEmptyName 测试失败。再次运行:
$ mvn clean test
[INFO] --- maven-surefire-plugin:3.0.0-M9:test (default-test) @ employee-system ---
[INFO] Running com.feixiang.employee.EmployeeValidatorTest
[ERROR] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0
[ERROR] com.feixiang.employee.EmployeeValidatorTest.shouldRejectEmptyName -- Time elapsed: 0.012 s <<< FAILURE!
[ERROR] expected: <false> but was: <true>
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[ERROR] There are test failures.
变化分析:
- 测试失败后,Maven 直接输出
BUILD FAILURE,不会继续执行package阶段 - 李眉的 Jenkins 流水线捕获到构建失败,自动阻止了问题代码的部署
- 黄俪的前端代码虽然不受影响,但整个项目的构建产物不会生成,确保了"有问题的后端代码不会流入生产环境"
易错点与常见问题
误区一:测试类命名不规范,Surefire 直接跳过
错误示例:把测试类命名为 EmployeeTestCase.java 以外的名字,如 EmployeeTests.java(某些旧版本 Surefire 不识别)或 TestEmployee.java(虽然识别,但不符合约定)。
纠正:严格遵循 *Test.java 命名规范。如果确实需要自定义测试类名模式,可以在 pom.xml 中配置:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M9</version>
<configuration>
<includes>
<include>**/*Tests.java</include>
</includes>
</configuration>
</plugin>
误区二:JUnit 依赖的 scope 不是 test
错误配置:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.2</version>
<!-- 漏写了 <scope>test</scope> -->
</dependency>
后果:JUnit 框架会被打包进最终的 JAR/WAR,污染生产环境。JUnit 只在编译和运行测试时需要,绝对不应该出现在生产产物中。
误区三:用 mvn package -DskipTests 跳过测试成为习惯
错误认知:"测试太慢了,我每次打包都加 -DskipTests,反正代码是我写的,不会有问题。"
纠正:-DskipTests 是应急手段(如紧急热修复),不是日常开发习惯。长期跳过测试会导致:
- 代码质量持续下降,回归 Bug 增多
- CI/CD 流水线失去意义(如果流水线也配了 skipTests)
- 新成员小崔修改代码时,不知道是否破坏了原有逻辑
正确做法:日常开发让 Surefire 正常工作;确实需要跳过测试时,明确记录原因并在任务完成后恢复。
小结
maven-surefire-plugin 是 Maven 构建流程中的"质检员",它在 test 阶段自动执行单元测试,测试通过则构建继续,测试失败则构建中断。通过生成 XML/HTML 测试报告,它为 CI/CD 流水线提供了可解析的质量数据。遵循测试类命名规范、确保测试依赖的 scope 为 test、不滥用跳过测试参数,是使用 Surefire 的三大最佳实践。
本章与全局的关系:本章讲解了编译后的测试执行环节。下一节将讲解 maven-war-plugin,它负责把通过测试的代码打包为 Web 应用部署包。