嘿,看到标题里的“实战”两个字,我就知道你没打算只听理论。咱们直接开干,别整那些虚头巴脑的“测试很重要”的开场白了。我做过无数个项目,从最初手点界面点点点,到后来完全靠代码跑回归,中间踩过的坑能写满一本笔记本。今天这篇,就是一次性把你从“环境配到怀疑人生”到“一键生成报告”这个过程彻底捋顺。
先聊聊为什么是 Maven 和 JUnit 5
你可能知道有 TestNG,也有 pytest,甚至有人还在用 TestNG。没错,但在 Java 生态里,Maven 配上 JUnit 5 是目前最稳、文档最全、社区最活跃的组合。尤其是 JUnit 5,它不是简单的更新,而是彻底重构了架构,支持 lambda 表达式、参数化测试、嵌套测试这些现代写法。你写出来的测试代码会非常干净,不像 JUnit 4 那样满篇的 @Before 和 @After。
咱们假设你已经装好了 JDK 17+ 和 Maven 3.8+,IntelliJ IDEA 或者 VS Code 随便你选。如果没有,先去官网下个,别用那些乱七八糟的破解版,后面配置依赖的时候容易出问题。
第一步:Maven 项目的“骨架”搭建
新建一个 Maven 项目,src/main/java 放业务代码,src/test/java 放测试代码。这是标准结构,别乱改。
在你的 pom.xml 里,你需要引入三个核心依赖。别急,我来解释每个都是干啥的:
- JUnit 5 Engine:这是执行测试的核心。
- Assertions API:虽然 JUnit 内置了,但有时候我们需要更丰富的断言库,不过默认够用了。
- JUnit Jupiter API:这是写测试用的注解和工具类。
最关键的是 maven-surefire-plugin。这个插件是 Maven 用来运行测试的“发动机”。如果版本不对,或者配置不对,你的 mvn test 可能运行了但报告没生成,或者根本跑不起来。
下面是我亲测能用的 pom.xml 片段,直接复制改改就能用:
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<junit.version>5.10.2</junit.version>
<surefire.version>3.2.5</surefire.version>
<!-- 这个是生成 HTML 报告的关键,稍后细说 -->
<surefire-reports.output>target/surefire-reports</surefire.reports.output>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- 如果要用 Mockito 做 mock,加上这个 -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire.version}</version>
<configuration>
<!-- 开启 parallel 并行执行,速度快 -->
<parallel>methods</parallel>
<threadCount>4</threadCount>
<!-- 这个配置确保生成 XML 报告,HTML 报告需要额外插件 -->
<useFile>true</useFile>
<reportFormat>plain</reportFormat>
</configuration>
</plugin>
<!-- 生成 HTML 报告的插件,推荐用 surefire 自带的或者 jacoco 结合 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.12.1</version>
</plugin>
</plugins>
</build>
注意:maven-surefire-plugin 版本一定要和 JUnit 版本兼容。我推荐用 3.0 以上版本,因为它们对 JUnit 5 的支持更好。如果你发现 mvn test 没反应,先检查这个版本。
第二步:写一个“像人话”的测试类
很多教程里写的测试代码看着就头疼,满屏的 assertEquals(expected, actual),顺序还反了。咱们换个写法,用 JUnit 5 的 lambda 风格,读起来像句子。
假设我们有一个简单的 Calculator 类:
package com.example.service;
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public double divide(double a, double b) throws Exception {
if (b == 0) throw new ArithmeticException("除数不能为0");
return a / b;
}
}
对应的测试类,我怎么写:
package com.example.service;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.*;
@DisplayName("计算器功能测试")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class CalculatorTest {
private Calculator calculator;
// 每个测试方法执行前初始化,别再用 @BeforeEach 啰嗦了,直接 here
@BeforeEach
void setUp() {
calculator = new Calculator();
}
// 测试用例1:简单加法
@Test
@DisplayName("测试两个正数相加")
@Order(1)
void testAddTwoPositiveNumbers() {
int result = calculator.add(2, 3);
assertEquals(5, result, "2 + 3 应该等于 5");
}
// 测试用例2:参数化测试,一次跑多个数据
@ParameterizedTest
@CsvSource({
"10, 5, 5", // 10 - 5 = 5
"100, 20, 80", // 100 - 20 = 80
"0, 0, 0" // 0 - 0 = 0
})
@DisplayName("参数化测试减法逻辑")
void testSubtract(int a, int b, int expected) {
// 假设 Calculator 有 subtract 方法,这里演示参数化写法
// int result = calculator.subtract(a, b);
// assertEquals(expected, result);
System.out.println("测试数据: " + a + " - " + b + " = " + expected);
}
// 测试用例3:异常测试
@Test
@DisplayName("除数为0时抛出异常")
void testDivideByZero() {
Exception exception = assertThrows(ArithmeticException.class, () -> {
calculator.divide(10, 0);
});
assertEquals("除数不能为0", exception.getMessage());
}
// 测试用例4:嵌套测试,逻辑分组
@Nested
@DisplayName("边界值测试")
class BoundaryTests {
@Test
void testAddNegativeNumbers() {
assertEquals(-5, calculator.add(-2, -3));
}
}
}
这里有个细节:@DisplayName 是 JUnit 5 的神器。它让测试报告里的名字不再是 testAddTwoPositiveNumbers 这种机器码,而是“测试两个正数相加”。等你看到报告时,会非常感谢这个设置。
第三步:运行测试,看懂输出
现在,在终端运行:
mvn clean test
你会看到控制台刷出一堆日志。别慌,重点看最后几行:
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.example.service.CalculatorTest
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] BUILD SUCCESS
如果 BUILD FAILURE,别急着骂人。往下翻,找 FAILURE 或者 ERROR 关键字。比如:
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.123 s <<< FAILURE! - in com.example.service.CalculatorTest
[ERROR] testAddTwoPositiveNumbers(com.example.service.CalculatorTest) Time elapsed: 0.01 s <<< FAILURE!
org.opentest4j.AssertionFailedError: 2 + 3 应该等于 5 ==> expected: <5> but was: <6>
这意味着你的 Calculator.add 方法可能写错了,或者你断言的时候期望值写错了。AssertionFailedError 是最常见的失败类型,说明 assertEquals 不通过。
第四步:生成漂亮的 HTML 报告
控制台输出太干了,老板和客户看不懂。我们需要一个 HTML 报告。Maven 原生支持生成 JUnit 风格的 XML 报告,但 HTML 报告需要借助插件。
有两个主流方案:
方案 A:使用 maven-surefire-plugin 自带的报告(最简单)
在 pom.xml 的 <build> 部分,确保 surefire 插件配置了 reportFormat 为 plain 或 xml。默认情况下,surefire 会在 target/surefire-reports 目录下生成文本报告和 XML 报告。
但这还不是 HTML。我们需要用 maven-site-plugin 来聚合这些报告。
在 pom.xml 的 <reporting> 部分加上:
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId>
<version>3.2.5</version>
</plugin>
</plugins>
</reporting>
然后运行:
mvn site
执行完成后,在 target/site/index.html 就能看到报告了。这里有一个“Surefire Report”链接,点进去就能看到详细的测试通过/失败列表。
缺点:样式比较老气,而且如果测试很多,页面会很长。
方案 B:使用 cucumber-report 或 jacoco 结合(推荐)
如果你想要更现代、更美观的报告,或者需要代码覆盖率,我推荐用 jacoco。它能生成覆盖率报告,并且可以和 surefire 集成。
在 pom.xml 中加入 jacoco 插件:
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.11</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
运行 mvn test 后,会在 target/site/jacoco/index.html 生成覆盖率报告。这个报告非常直观,能看到每个类、每个方法的覆盖率,还能看到哪些行没被执行到(红色标记)。
实战技巧:把 surefire 报告和 jacoco 报告合并。在 maven-site-plugin 的配置中,同时包含两个报告源。这样 mvn site 生成的一个页面里既有测试结果,又有覆盖率,非常专业。
第五步:CI/CD 集成,让报告自动飞起来
手动跑 mvn test 只适合本地开发。在团队里,我们需要自动化。比如用 Jenkins、GitLab CI 或者 GitHub Actions。
以 GitHub Actions 为例,创建一个 .github/workflows/test.yml:
name: Java CI with Maven
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: maven
- name: Build and Test with Maven
run: mvn -B package --file pom.xml
- name: Upload Test Results
if: always() # 即使测试失败也上传
uses: actions/upload-artifact@v3
with:
name: surefire-reports
path: target/surefire-reports/
- name: Upload Coverage Report
if: always()
uses: actions/upload-artifact@v3
with:
name: jacoco-report
path: target/site/jacoco/
这样,每次你 push 代码到 GitHub,流水线会自动运行测试,并把报告作为 artifact 保存下来。你可以下载 surefire-reports 的 ZIP 包,里面有你想要的 XML 和文本报告。如果需要 HTML,可以在流水线最后加一步 mvn site,然后上传 target/site 目录。
第六步:常见坑和解决方案
坑1:测试没跑起来
- 检查
pom.xml中maven-surefire-plugin的版本。 - 检查测试类是否放在
src/test/java下。 - 检查测试类名是否以
Test结尾,或者方法是否以test开头(默认约定)。 - 确认依赖作用域是
<scope>test</scope>。
坑2:报告里中文乱码
- 在
pom.xml的maven-surefire-plugin配置中加入:
<configuration>
<argLine>-Dfile.encoding=UTF-8</argLine>
</configuration>
- 或者在 IntelliJ 的 Run Configuration 里,VM options 加上
-Dfile.encoding=UTF-8。
坑3:测试依赖数据库,本地跑不通
- 使用
@ActiveProfiles("test")配合 Spring Boot,切换配置。 - 或者用
H2内存数据库替代真实数据库。 - 或者用
Testcontainers启动一个临时的 Docker 容器作为测试环境。这是最干净的做法,虽然配置稍复杂,但值得。
坑4:测试执行太慢
- 开启 parallel 执行(前面已经提过)。
- 检查测试之间是否有状态依赖,尽量让每个测试独立。
- 用
@Disabled暂时跳过那些耗时但已知的测试。
最后,聊聊测试的价值
很多人觉得写测试麻烦,影响开发进度。我理解这种感受,尤其是项目紧急的时候。但你要知道,测试是你代码的“保险丝”。当你重构代码时,测试会告诉你是否破坏了原有功能。当新同事接手项目时,测试用例就是最好的文档。
不要追求 100% 的覆盖率,那通常是浪费。聚焦在核心业务逻辑、边界条件和异常处理上。把测试当作产品的一部分,而不是附属品。
现在,去你的项目里试一下吧。从最简单的 assertEquals 开始,慢慢加入参数化测试、嵌套测试。遇到报错别慌,读日志,查文档。慢慢地,你就会发现,写测试比写业务代码还顺手。
如果有具体的报错信息或者想深入了解某个插件的配置,随时问我。咱们在实战中解决问题,比看十遍文档都管用。
