> 为 Java/Maven 项目生成 JUnit 5 + Mockito 单元测试,覆盖增量代码变更,目标覆盖率 90%~100%
增量代码感知:
main(或 master)分支获取增量变更全量测试生成:
全 Mock 策略:
源码深度读取:
仅测源码:
Test.java、Spec.java、*TestCase.java 等文件当用户提到以下关键词时激活:
与全项目测试生成的区别:
| 工具 | 用途 | 检查方式 |
|---|---|---|
| ------ | ------ | ---------- |
| Git | 获取增量代码差异 | git --version |
| Maven | 解析依赖、运行测试 | mvn --version |
在生成测试前,检查项目 pom.xml 是否包含以下依赖:
第一步:检测 JDK 版本
读取 pom.xml 中的 或 属性,确定目标 JDK 版本:
| JDK 版本 | junit-jupiter | mockito-core | mockito-junit-jupiter |
|---|---|---|---|
| ---------- | --------------- | -------------- | ---------------------- |
| Java 8 | 5.9.x | 4.11.0 | 4.11.0 |
| Java 11+ | 5.9.x | 5.5.0 | 5.5.0 |
> 重要: Mockito 5.x 编译后的 class 文件版本为 55.0 (Java 11+),在 Java 8 项目中使用会导致编译失败。必须根据 JDK 版本选择对应的 Mockito 版本。
第二步:检查依赖是否存在
| 依赖 | 版本(按 JDK) | 作用域 |
|---|---|---|
| ------ | --------------- | -------- |
org.junit.jupiter:junit-jupiter | Java 8→5.9.3 / Java 11+→5.9.3 | test |
org.mockito:mockito-core | Java 8→4.11.0 / Java 11+→5.5.0 | test |
org.mockito:mockito-junit-jupiter | Java 8→4.11.0 / Java 11+→5.5.0 | test |
可选但推荐:
org.mockito:mockito-inline — 支持 static/final 方法 mock缺失处理:
pom.xmlmvn test-compile 验证# 1. 确认是 Maven 项目(存在 pom.xml)
# 2. 检查测试依赖是否存在于 pom.xml
# 3. 确认是 Java 项目(src/main/java 目录存在)
输出: 依赖状态报告(完整 / 缺失需补充)
# 1. 确认默认分支(main 优先,不存在则 master)
git rev-parse --verify main 2>/dev/null || git rev-parse --verify master 2>/dev/null
# 2. 获取增量变更(与默认分支对比)
git diff <default-branch> --name-only --diff-filter=ACMR -- '*.java'
# 3. 排除测试文件
# 过滤掉 src/test/java 路径下的文件
# 4. 获取具体方法级变更
git diff <default-branch> -- src/main/java/包路径/类名.java
# 5. 解析变更类型
# - 新增类
# - 新增方法
# - 修改方法
# - 修改字段/依赖
输出: 增量变更清单(类 -> 方法 -> 变更类型)
# 对每个变更的源文件,检查对应测试类是否存在
# 源文件:src/main/java/com/example/service/UserService.java
# 测试文件:src/test/java/com/example/service/UserServiceTest.java
# 规则:
# - 测试类存在且测试方法已覆盖变更方法 -> 跳过
# - 测试类存在但未覆盖变更方法 -> 补充测试方法
# - 测试类不存在 -> 全量生成
在生成测试前,对目标类执行以下读取:
构造对象原则:
Lombok 特殊处理(必须检查):
@Setter(AccessLevel.NONE) 或 @Setter(AccessLevel.PROTECTED) 注解electricityAmount.add(serviceAmount)),应设置参与计算的源字段@Accessors(chain = true) —— 此时 setter 返回自身而非 void,可直接链式调用@FieldDefaults —— 可能影响字段访问级别根据 Step 2 的结果选择策略:
@Mock + Mockito 模拟@InjectMocks 注入被测实例跳过规则(满足任一即跳过):
@Data, @Getter, @Setter)toString()、equals()、hashCode()(除非手动实现且含业务逻辑)必须测试(满足任一即生成):
# 1. 编译测试代码
mvn test-compile -q
# 2. 如果编译失败,检查并修复
# - 导入缺失
# - 方法签名不匹配
# - 类型不匹配
# 3. 运行单个测试类验证
mvn test -Dtest=ClassNameTest -q
# 使用 JaCoCo 验证覆盖率
mvn test jacoco:report -q
# 检查覆盖率报告
# target/site/jacoco/index.html
# 目标:方法覆盖率 >= 90%
未达标处理:
源类名:UserService.java
测试类名:UserServiceTest.java
源类名:OrderServiceImpl.java
测试类名:OrderServiceImplTest.java
// 格式:[方法名]_[场景描述]_[期望结果]
@Test
void createOrder_validInput_returnsCreatedOrder() { }
@Test
void createOrder_nullInput_throwsIllegalArgumentException() { }
@Test
void getUser_notFound_returnsEmptyOptional() { }
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private EmailService emailService;
@Mock
private RedisTemplate<String, Object> redisTemplate;
@InjectMocks
private UserService userService;
// 所有外部依赖都 @Mock,不写真实调用
}
对每个被测 public 方法,至少覆盖:
| 场景 | 优先级 |
|---|---|
| ------ | -------- |
| 正常输入,正常返回 | P0 |
| 输入为 null | P0 |
| 输入为空集合/空字符串 | P1 |
| 依赖抛出异常 | P1 |
| 分支条件各路径 | P0 |
| 边界值(0、-1、MAX) | P1 |
| 并发/线程安全(如适用) | P2 |
详见 references/test-template.md
package com.example.service;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.BeforeEach;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
@DisplayName("UserService 单元测试")
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@BeforeEach
void setUp() {
// 通用 mock 设置(如有)
}
@Test
@DisplayName("创建用户 - 正常路径")
void createUser_validInput_returnsUser() {
// Given
UserDTO dto = new UserDTO();
dto.setName("test");
dto.setEmail("test@example.com");
when(userRepository.save(any(User.class))).thenReturn(new User("test"));
// When
User result = userService.createUser(dto);
// Then
assertNotNull(result);
assertEquals("test", result.getName());
verify(userRepository).save(any(User.class));
}
}
null 或 0,但字段已设置症状: 测试中通过 setField(value) 设置了值,但 obj.getField() 返回 null 或默认值。
排查步骤:
@Setter(AccessLevel.NONE) —— 如果有,getter 很可能也是计算型的示例:
// 实际代码中:
// @Setter(AccessLevel.NONE)
// private BigDecimal chargeMoneyAmount;
// public BigDecimal getChargeMoneyAmount() {
// return electricityAmount.add(serviceAmount); // 计算型 getter
// }
// ❌ 错误:设置目标字段无效,因为 getter 不读它
field.set(order, BigDecimal.valueOf(6.0));
// ✅ 正确:设置参与计算的源字段
order.setElectricityAmount(BigDecimal.valueOf(6.0));
order.setServiceAmount(BigDecimal.ZERO);
any() 匹配器导致 NullPointerException症状: when(mock.method(any(SomeClass.class))).thenReturn(...) 在 stub 注册阶段抛出 NPE。
原因: 某些接口(特别是外部模块的接口)的方法在接收 null 参数时会直接抛出异常,而 any() 在 stub 注册阶段会先以 null 参数试探性调用。
解决: 使用 doReturn() + nullable(Class) 匹配器:
doReturn(result).when(mockService).method(
ArgumentMatchers.<SomeClass>nullable(SomeClass.class)
);
症状: PotentialStubbingProblem 异常,提示实际调用的参数数量与 stub 不一致。
解决:
when() 参数数量与实际调用一致nullable() 匹配器覆盖所有参数@MockitoSettings(LENIENT) vs lenient().when()原则: 优先使用 lenient().when() 针对个别 stub,避免全局 LENIENT。
// ✅ 推荐:仅对特定 stub 放宽
lenient().when(redisUtils.set(anyString(), any())).thenReturn(true);
// ⚠️ 谨慎使用:全局 lenient 会隐藏其他 stubbing 问题
@MockitoSettings(strictness = Strictness.LENIENT)
只有当多个 stub 存在参数数量/类型差异且难以精确匹配时,才使用全局 LENIENT。
问题:项目中不存在 pom.xml
解决:
1. 确认当前目录是否为 Maven 项目根目录
2. 如果是子模块,找到对应的 pom.xml
3. 如果确实不是 Maven 项目,提示用户本 skill 仅支持 Java/Maven
问题:当前目录不是 Git 仓库
解决:
1. 如果用户指定了具体类/包路径,跳过增量分析,直接全量生成
2. 如果用户要求"增量测试"但无 Git,提示用户初始化 git 仓库
问题:git rev-parse --verify main 和 master 都失败
解决:
1. 列出所有远程分支:git branch -r
2. 询问用户应对比哪个分支
3. 或使用 git diff HEAD~1 作为替代(最近一次提交)
问题:mvn test-compile 失败
解决:
1. 检查生成的测试代码导入是否正确
2. 检查方法签名是否与源类匹配
3. 检查 Mockito when/thenReturn 的返回类型
4. 修复后重新编译
问题:JaCoCo 报告显示覆盖率低于 90%
解决:
1. 读取 JaCoCo 报告,识别未覆盖的方法/分支
2. 为每个未覆盖点生成补充测试
3. 重新运行覆盖率检查
4. 若仍有无法覆盖的分支(如防御性代码),在报告中说明原因
问题:源码引用的依赖类在类路径中找不到
解决:
1. 检查是否为外部依赖(需 mvn dependency:resolve)
2. 检查是否为同项目其他模块(需先编译依赖模块)
3. 对找不到的依赖,使用 mock 占位,在测试注释中说明
用户:为 UserService 生成单元测试
AI: 收到!开始执行单元测试生成流程:
Step 0: 项目扫描...
✅ Maven 项目确认
✅ JUnit 5 + Mockito 依赖存在
Step 1: 目标类分析...
📄 com.example.service.UserService (120 行, 8 个 public 方法)
Step 2: 测试类存在性检查...
❌ UserServiceTest.java 不存在 -> 全量生成
Step 3: 源码深度读取...
📖 UserService.java - 8 个 public 方法
📖 UserRepository.java - 依赖接口
📖 User.java - Entity 定义
📖 UserDTO.java - 参数对象
Step 4: 全量测试生成...
✅ 生成 12 个测试方法(覆盖 8 个 public 方法)
📝 UserServiceTest.java (350 行)
Step 5: 编译验证...
✅ mvn test-compile 通过
✅ mvn test -Dtest=UserServiceTest 通过(12/12 passed)
Step 6: 覆盖率验证...
✅ 方法覆盖率:95%(19/20 方法已覆盖)
✅ 分支覆盖率:88%
✅ 完成!测试类已生成:src/test/java/com/example/service/UserServiceTest.java
用户:为这次的增量代码生成单元测试
AI: 收到!开始执行增量测试流程:
Step 0: 项目扫描...
✅ Maven 项目确认
✅ 测试依赖存在
Step 1: 增量代码分析(对比 main 分支)...
📊 发现 3 个变更文件:
- UserService.java (+2 个方法)
- OrderController.java (修改 1 个方法)
- PaymentHelper.java (新增类)
Step 2: 测试类存在性检查...
✅ UserServiceTest.java 存在 -> 检查覆盖
✅ OrderControllerTest.java 存在 -> 检查覆盖
❌ PaymentHelperTest.java 不存在 -> 全量生成
Step 3: 增量方法覆盖分析...
UserServiceTest:
❌ verifyEmail() - 未覆盖 -> 补充
❌ batchUpdate() - 未覆盖 -> 补充
OrderControllerTest:
❌ updateOrderStatus() - 已修改,现有测试不覆盖新逻辑 -> 补充
PaymentHelperTest:
-> 全量生成(5 个 public 方法)
Step 4: 测试生成...
✅ UserServiceTest.java 补充 2 个测试方法
✅ OrderControllerTest.java 补充 1 个测试方法
✅ PaymentHelperTest.java 全量生成 8 个测试方法
Step 5: 编译验证...
✅ 全部通过
Step 6: 覆盖率验证...
✅ 变更方法覆盖率:100%
✅ 完成!共补充/生成 11 个测试方法
用户:为 OrderService 生成单元测试
AI: 收到!开始执行单元测试生成流程:
Step 0: 项目扫描...
✅ Maven 项目确认
❌ 缺少 JUnit 5 依赖
❌ 缺少 Mockito 依赖
⚠️ 检测到项目缺少单元测试必需依赖:
需要在 pom.xml 中添加以下依赖:
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.5.0</version>
<scope>test</scope>
</dependency>
</dependencies>
是否自动添加到 pom.xml?(y/n)
用户:y
AI: ✅ 依赖已添加到 pom.xml
✅ mvn test-compile 验证通过
继续生成 OrderService 单元测试...
为确保 JUnit 5 正确运行,检查 pom.xml 中是否配置了 Surefire 插件:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.2</version>
</plugin>
</plugins>
</build>
<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>
| 版本 | 日期 | 作者 | 变更 |
|---|---|---|---|
| ------ | ------ | ------ | ------ |
| 1.0.0 | 2026-04-15 | endcy | 初始版本,支持 JUnit 5 + Mockito 增量/全量单元测试生成 |
共 1 个版本