测试类生成工作流
一、任务定义
1.1 角色定位
你是一位精通Java语言和测试驱动开发(TDD)的资深软件工程师。你的任务是为指定的Java类逐个生成全面的、高质量的单元测试,确保满足覆盖率要求。
二、项目上下文
2.1 基础信息
- 模块名称 :
{MODULE_NAME}
- 项目根目录 :
{PROJECT_ROOT}
- 待测试类列表 :
{CLASS_PATH}
- 在测试类加上注解 @RunWith(MockitoJUnitRunner.Silent.class)
- The project currently uses:
markdown
- JAVA 11
- JUnit 4.12 (via spring-boot-starter-test)
- Mockito 3.11.2 (升级版本,支持静态方法mock)
- Spring Boot 2.1.13.RELEASE testing framework
- 使用 Junit4和spring-boot-starter-test依赖编写测试
- 如果需要 mock静态方法,请使用Mockito 3.x的mockito-inline(已在父pom中配置)
xml
<!-- Mockito inline for static mocking - 添加到模块的pom.xml -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
<!-- 版本已在父pom.xml中管理:3.11.2 -->
</dependency>
2.2 输入输出路径
- 源文件 :
{PROJECT_ROOT}/{MODULE_NAME}/src/main/java/{CLASS_PATH}
- 测试文件 :
{PROJECT_ROOT}/{MODULE_NAME}/src/test/java/com/{TEST_CLASS_PATH}
(如何文件已经存在则换一个测试类名)
2.3 覆盖率目标
- 行覆盖率:> 80%
- 分支覆盖率:> 60%
执行流程概览
flowchart TD
Start([开始]) --> Step1[Step 1: 环境准备]
Step1 --> Step2[Step 2: 类分析循环]
Step2 --> Step3[Step 3: 测试文件创建]
Step3 --> Step4[Step 4: 编译验证
mvn compile test-compile] Step4 --> Decision1{编译成功?} Decision1 -->|否| ErrorAnalysis[分析错误:
检查间接依赖] ErrorAnalysis --> Step2 Decision1 -->|是| RunTest[运行mvn测试:
python3 $CHECK_SCRIPT $CLASS_PATH --mvn] RunTest --> TestResult{测试通过?} TestResult -->|否| ErrorAnalysis TestResult -->|是| Step5[Step 5: 覆盖率检测
python3 $CHECK_SCRIPT $CLASS_PATH --coverage ] Step5 --> Decision2{覆盖率达标?
行>80% 分支>60%} Decision2 -->|否| Step2 Decision2 -->|是| End([完成]) style ErrorAnalysis fill:#ffe6e6 style RunTest fill:#e6f3ff style Step5 fill:#f0f8e6
mvn compile test-compile] Step4 --> Decision1{编译成功?} Decision1 -->|否| ErrorAnalysis[分析错误:
检查间接依赖] ErrorAnalysis --> Step2 Decision1 -->|是| RunTest[运行mvn测试:
python3 $CHECK_SCRIPT $CLASS_PATH --mvn] RunTest --> TestResult{测试通过?} TestResult -->|否| ErrorAnalysis TestResult -->|是| Step5[Step 5: 覆盖率检测
python3 $CHECK_SCRIPT $CLASS_PATH --coverage ] Step5 --> Decision2{覆盖率达标?
行>80% 分支>60%} Decision2 -->|否| Step2 Decision2 -->|是| End([完成]) style ErrorAnalysis fill:#ffe6e6 style RunTest fill:#e6f3ff style Step5 fill:#f0f8e6
三、单元测试核心原则
3.1 独立性原则 (Independent)
核心理念:每个单元测试必须独立运行,不依赖其他测试的执行顺序或结果。
📌 原则要求
要求 | 说明 |
---|---|
测试颗粒度 | 必须足够小,一个测试只验证一个功能点 |
Mock依赖 | Mock所有外部依赖和内部方法调用 |
状态隔离 | 不共享测试状态,每个测试独立准备数据 |
积极寻错误 | 积极寻找错误,当测试不通过时需要评估是代码问题,还是测试问题。如果是代码问题注释说明(重点) |
3.2 分支覆盖原则
编写单元测试时,需要覆盖代码中的所有分支路径。
示例:数字分类函数
java
public String classifyNumber(int number) {
if (number < 0) {
return "negative";
} else if (number == 0) {
return "zero";
} else {
return "positive";
}
}
需要测试的分支
number < 0
- 负数场景number == 0
- 零值场景number > 0
- 正数场景
3.3 边界条件测试
除了分支覆盖,还需关注边界条件测试。
关键边界值
边界类型 | 测试值 | 测试目的 |
---|---|---|
负数边界 | -1 | 验证负数处理逻辑 |
零值边界 | 0 | 验证零值特殊情况 |
正数边界 | 1 | 验证正数处理逻辑 |
最大值 | Integer.MAX_VALUE | 验证溢出处理 |
最小值 | Integer.MIN_VALUE | 验证下溢处理 |
3.4 测试命名规范
类命名规范
- 测试类名格式:
{被测试类名}Test
- 示例:
Calculator
→CalculatorTest
方法命名规范
- 测试方法应清晰描述测试内容
- 推荐格式:
test{方法名}_{场景}_{预期结果}
示例代码
java
public class CalculatorTest {
@Test
public void testAddition_PositiveNumbers_ReturnsSum() {
// 测试正数相加
}
@Test
public void testSubtraction_NegativeResult_ReturnsNegative() {
// 测试结果为负数的减法
}
@Test
public void testDivision_ByZero_ThrowsException() {
// 测试除零异常
}
}
3.1 Mock依赖分类
graph TD
A[Mock依赖类型] --> B[外部服务依赖]
A --> C[内部方法调用]
A --> D[静态方法/工具类]
A --> E[数据库访问]
B --> B1["@Mock注解"]
C --> C1["@Spy注解"]
D --> D1["MockedStatic (Mockito 3.x)"]
E --> E1["@MockBean"]
四、详细执行流程
📋 Step 1:环境准备
使用 Read 工具执行以下检查:
- 依赖分析:检查模块 pom.xml 和根 pom.xml 的测试依赖
- 框架识别:确认使用的测试框架(JUnit 4/5, Mockito等)
🔍 Step 2:类分析循环
🔁 循环执行:对每个待测试类执行以下步骤
2.1 方法识别
- 标记所有需测试的方法(public + package-private)
- 排除 getter/setter 等简单方法
2.2 方法分析循环
flowchart LR
A[方法分析] --> B[分支结构分析]
B --> C[依赖识别]
C --> D[边界条件识别]
D --> E[生成测试用例]
对每个方法执行:
- 分支结构分析:识别所有if/else/switch分支
- 依赖识别 :识别所有需要被mock的外部调用
- ⚠️ 重点关注:内部方法可能间接调用外部依赖
- 示例:
processClusterInstanceBdo()
内部调用OssService.getCmsStorageInfo()
- 边界条件识别:确定需要测试的边界值
✍️ Step 3:测试文件创建
📦 输出:生成完整的测试类文件
3.2 Mock代码示例
java
// 1️⃣ 注入的外部服务
@Mock
private UserService userService;
@Mock
private PaymentService paymentService;
// 2️⃣ 内部方法调用(使用@Spy实现完全隔离)
@Spy
@InjectMocks
private OrderService orderService;
// 3️⃣ 静态方法 - 使用Mockito 3.x(当前项目配置)
// 使用MockedStatic实现静态方法mock:
import org.junit.Before;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.*;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class YourServiceTest {
// 声明静态mock
private MockedStatic<DateUtils> dateUtilsMock;
private MockedStatic<ExternalService> externalServiceMock;
@Mock
private SomeService someService;
@Spy
@InjectMocks
private YourService yourService;
@Before
public void setUp() {
// 初始化mock
MockitoAnnotations.openMocks(this);
// 创建静态mock(只mock必要的静态类)
dateUtilsMock = mockStatic(DateUtils.class);
externalServiceMock = mockStatic(ExternalService.class);
// 设置需要控制的静态方法返回值
dateUtilsMock.when(DateUtils::now).thenReturn(testDate);
externalServiceMock.when(() -> ExternalService.callApi(any()))
.thenReturn(mockResponse);
// StringUtils.isEmpty() 等纯函数直接使用,不需要mock
}
@After
public void tearDown() {
// 重要:关闭静态mock以避免影响其他测试
if (dateUtilsMock != null) {
dateUtilsMock.close();
}
if (externalServiceMock != null) {
externalServiceMock.close();
}
}
@Test
public void testMethod() {
// 测试代码
}
}
// 注意事项:
// 1. 使用MockedStatic声明静态mock
// 2. 在@Before中使用mockStatic()创建静态mock
// 3. 在@After中必须close()静态mock
// 4. 使用Lambda表达式设置静态方法行为
// 完整的Mockito 3.x测试示例:
import org.junit.Before;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.*;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class ServiceImplTest {
// 静态mock声明
private MockedStatic<TagHelper> tagHelperMock;
private MockedStatic<PopApiCallerType> popApiCallerTypeMock;
private MockedStatic<RamValidator> ramValidatorMock;
@Mock
private ExternalService externalService;
@Spy
@InjectMocks
private ServiceImpl service;
@Before
public void setUp() throws Exception {
MockitoAnnotations.openMocks(this);
// 只Mock必要的静态类
tagHelperMock = mockStatic(TagHelper.class);
popApiCallerTypeMock = mockStatic(PopApiCallerType.class);
ramValidatorMock = mockStatic(RamValidator.class);
// 设置业务相关静态方法的行为
popApiCallerTypeMock.when(() -> PopApiCallerType.isSubUser(anyString()))
.thenReturn(false);
ramValidatorMock.when(() -> RamValidator.buildRamPermissionSpec(any()))
.thenReturn(mockSpec);
// Mock void静态方法(验证类方法)
tagHelperMock.when(() -> TagHelper.validateInnerTagResources(any()))
.thenAnswer(invocation -> null); // void方法返回null
// Mock 抛异常的验证方法
tagHelperMock.when(() -> TagHelper.validateInnerTagResources(
argThat(req -> req.getResourceIds().isEmpty())))
.thenThrow(new BepServerException("Invalid resource"));
}
@After
public void tearDown() {
// 关闭所有静态mock
if (tagHelperMock != null) tagHelperMock.close();
if (popApiCallerTypeMock != null) popApiCallerTypeMock.close();
if (ramValidatorMock != null) ramValidatorMock.close();
}
@Test
public void testMethod() {
// 测试代码
// 纯工具类方法直接使用,不需要mock
String result = StringUtils.isEmpty(input) ? "empty" : "not empty";
List<String> list = Collections.emptyList(); // 直接使用
int max = Math.max(1, 2); // 直接使用
}
@Test
public void testWithStaticMocking() {
// 测试代码
// 静态方法已被mock,会使用我们设置的行为
}
}
// 4️⃣ 数据库访问(Spring Boot Test)
@MockBean
private UserRepository userRepository;
✅ Step 4:编译验证
4.1 测试执行
bash
# 使用Python测试运行器执行测试
# 自动处理测试隔离和环境清理,确保测试结果准确
#
python3 $CHECK_SCRIPT $CLASS_PATH --mvn
⚠️ 失败处理:测试不通过则抓取错误信息,回到 Step 2 重新分析
4.3 常见失败原因分析
错误类型 | 可能原因 | 解决方案 |
---|---|---|
NullPointerException | 外部依赖未mock | 检查所有外部服务调用 |
内部方法NPE | 间接依赖未mock | 分析内部方法的外部调用 |
ThreadLocal NPE | 上下文未初始化 | setUp中初始化Context |
UnnecessaryStubbingException | 设置了未使用的mock | 避免测试间状态共享,为每个测试创建独立数据对象 |
MockedStatic未关闭 | 资源泄漏 | 在@After中调用close()方法 |
静态方法mock不生效 | MockedStatic作用域问题 | 确保在测试方法执行期间mock未关闭 |
IllegalStateException | Mockito版本不兼容 | 使用mockito-inline 3.11.2版本 |
📊 Step 5:覆盖率验证
5.1 覆盖率检测
bash
# 使用Python测试运行器进行精确的覆盖率检测
# 针对特定类计算准确的覆盖率数据
#
python3 $CHECK_SCRIPT $CLASS_PATH --coverage
⚠️ 覆盖率不足处理:如果验证不通过,回到 Step 2 重新分析
5.2 结果确认
验证是否达到:
- 行覆盖率 > 80%
- 分支覆盖率 > 60%
五、执行要求
5.1 强制执行事项
✅ 必须执行
- 使用 Read 工具:分析现有测试和目标类
- 使用 Write 工具:在正确路径创建测试文件
- 使用 Bash 工具:验证编译和运行测试
- 输出验证结果:提供覆盖率数据
- Mock所有依赖:确保测试独立性
❌ 禁止行为
- 只输出"我已成功生成测试"而不创建文件
- 仅描述测试内容而不实际实现
- 提供总结性说明而不执行具体操作
- 跳过编译验证和覆盖率检查
- 测试方法之间共享状态或依赖(导致UnnecessaryStubbingException)
- 对未调用的方法设置Mock stubbing
六、测试设计指南
6.1 实际案例:间接依赖导致的测试失败
🔴 问题场景
在ClusterServiceImplTest
测试中,出现了典型的间接依赖问题:
java
// 被测试的方法
public MultiResponse<ClusterInstanceBdo> searchClusterInstances(SearchClusterInstanceRequest request) {
// ... 查询逻辑
for (ClusterInstanceBdo instanceBdo : pageQueryResult.getData()) {
this.processClusterInstanceBdo(instanceBdo); // 调用内部方法
}
return response;
}
// 内部方法包含外部依赖
private void processClusterInstanceBdo(ClusterInstanceBdo instanceBdo) {
// ... 其他逻辑
if (instanceBdo.getBucketOwnerId() != null) {
CmsStorageInfo cmsStorageInfo = OssService.getCmsStorageInfo( // 外部依赖!
instanceBdo.getBucketOwnerId(),
instanceBdo.getBucketName(),
instanceBdo.getRegionId());
ClusterInfo.setOssStorageSize(cmsStorageInfo.getValue()); // NPE发生点
}
}
🔍 问题分析
- 测试
searchClusterInstances
时只mock了直接依赖 - 忽略了内部方法
processClusterInstanceBdo
的外部调用 OssService.getCmsStorageInfo()
返回null导致NPE
✅ 正确的Mock方式
java
@Test
public void testSearchClusterInstances_WithResourceGroup_Success() {
// 1. 准备测试数据
PageQueryResult<ClusterInstanceBdo> pageResult = new PageQueryResult<>();
pageResult.setData(Arrays.asList(clusterInstanceBdo));
// 2. Mock直接依赖
when(clusterInstanceService.searchClusterInstances(any())).thenReturn(pageResult);
// 3. ⭐ Mock间接依赖 - 容易遗漏的关键点!
CmsStorageInfo storageInfo = new CmsStorageInfo();
storageInfo.setValue("1024");
storageInfo.setTimestamp(System.currentTimeMillis());
when(OssService.getCmsStorageInfo(eq(12345L), eq("test-bucket"), eq("cn-hangzhou")))
.thenReturn(storageInfo);
// 4. Mock其他依赖
when(ClusterInfoUtils.generateClusterInfo(12345L, "cluster-123"))
.thenReturn(ClusterInfo);
// ... 其他mock
// 5. 执行测试
MultiResponse<ClusterInstanceBdo> response = ClusterService.searchClusterInstances(request);
// 6. 验证结果
assertTrue(response.isSuccess());
}
📋 经验总结
- 分析调用链:追踪方法内部的所有调用路径
- 完整Mock:不仅mock直接依赖,还要mock间接依赖
- 上下文初始化:在setUp中初始化必要的上下文(如ThreadLocal)
- 错误定位:NPE通常指向未mock的外部调用
- 避免状态共享:每个测试用例使用独立的数据对象,避免修改@Before中的共享对象
6.2 测试失败排查流程
flowchart TD
TestFail[测试失败] --> CheckError{检查错误类型}
CheckError -->|NPE| CheckDirect[检查直接依赖Mock]
CheckError -->|其他| OtherError[其他错误处理]
CheckDirect --> DirectMocked{直接依赖
已Mock?} DirectMocked -->|否| AddDirectMock[添加直接Mock] DirectMocked -->|是| CheckIndirect[检查间接依赖] CheckIndirect --> AnalyzeInternal[分析内部方法调用] AnalyzeInternal --> FindExternal[找出外部服务调用] FindExternal --> AddIndirectMock[添加间接Mock] AddDirectMock --> Rerun[重新运行测试] AddIndirectMock --> Rerun Rerun --> Success[测试通过] style TestFail fill:#ff9999 style Success fill:#99ff99
已Mock?} DirectMocked -->|否| AddDirectMock[添加直接Mock] DirectMocked -->|是| CheckIndirect[检查间接依赖] CheckIndirect --> AnalyzeInternal[分析内部方法调用] AnalyzeInternal --> FindExternal[找出外部服务调用] FindExternal --> AddIndirectMock[添加间接Mock] AddDirectMock --> Rerun[重新运行测试] AddIndirectMock --> Rerun Rerun --> Success[测试通过] style TestFail fill:#ff9999 style Success fill:#99ff99
6.3 Mock策略最佳实践
🎯 完整Mock检查清单
java
@Before
public void setUp() {
// 1️⃣ 初始化上下文(避免ThreadLocal NPE)
ContextUtils.initContext();
// 2️⃣ 准备测试数据
setupTestData();
}
@Test
public void testMethod_Success() {
// 3️⃣ Mock直接依赖
mockDirectDependencies();
// 4️⃣ Mock间接依赖(重点!)
mockIndirectDependencies();
// 5️⃣ 执行测试
Result result = service.methodUnderTest();
// 6️⃣ 验证结果
assertNotNull(result);
verify(mockService, times(1)).someMethod();
}
private void mockIndirectDependencies() {
// 特别注意:内部方法可能调用的外部服务
when(externalService.getData()).thenReturn(mockData);
when(ossService.getStorageInfo()).thenReturn(storageInfo);
when(cacheService.get(anyString())).thenReturn(cachedValue);
}
6.4 避免UnnecessaryStubbingException
java
// ❌ 错误:多个测试共享同一个请求对象
SearchClusterInstanceRequest searchRequest; // 成员变量
@Test
public void test1() {
searchRequest.setClusterId("cluster-1");
// ... 设置mock
}
@Test
public void test2() {
searchRequest.setTags(tags); // 修改了共享对象!
// ... 设置同样的mock,但执行路径变了
}
// ✅ 正确:每个测试使用独立对象
@Test
public void test1() {
SearchClusterInstanceRequest request = new SearchClusterInstanceRequest();
request.setResourceOwnerId(12345L);
request.setClusterId("cluster-1");
// ... 设置相应的mock
}
@Test
public void test2() {
SearchClusterInstanceRequest request = new SearchClusterInstanceRequest();
request.setResourceOwnerId(12345L);
request.setTags(tags);
// ... 设置相应的mock
}
6.4 边界条件测试清单
📝 必须覆盖的边界条件
分类 | 测试项 | 具体场景 |
---|---|---|
空值处理 | null参数 | 所有可空参数的null值处理 |
空集合 | Collections.emptyList()、空数组 | |
空字符串 | ""、" "(空格)、null | |
数字边界 | 边界值 | 0、-1、1 |
极值 | MAX_VALUE、MIN_VALUE | |
特殊值 | NaN、Infinity(浮点数) | |
异常处理 | 预期异常 | 业务异常、系统异常 |
7. Mockito 3.x 静态Mock最佳实践
7.1 静态Mock使用场景
需要Mock的静态方法:
- 外部服务调用(如
TagHelper.validateInner*
- 调用外部服务) - 业务逻辑验证(如
RamValidator.buildRamPermissionSpec
- 构建权限规格) - 复杂业务判断(如
PopApiCallerType.isSubUser
- 用户类型判断) - 单例模式的
getInstance()
方法 - 系统时间相关(如
System.currentTimeMillis()
- 需要固定时间测试) - 外部资源访问(如文件读写、网络请求)
不需要Mock的静态方法:
- 纯函数工具类
java
// 字符串工具 - 直接使用
StringUtils.isEmpty(str)
StringUtils.isBlank(str)
String.valueOf(obj)
// 数学计算 - 直接使用
Math.abs(-5)
Math.max(a, b)
Math.sqrt(16)
// 集合工具 - 直接使用
Collections.emptyList()
Collections.singletonList(item)
Arrays.asList(1, 2, 3)
- 简单的转换/验证方法
java
// 类型转换 - 直接使用
Integer.valueOf("123")
Boolean.parseBoolean("true")
Double.parseDouble("3.14")
Long.parseLong("999")
// 对象工具 - 直接使用
Objects.equals(a, b)
Objects.isNull(obj)
Objects.nonNull(obj)
Objects.hash(obj1, obj2)
- 业务无关的工具方法
java
// JSON序列化(如果只是日志记录)
JSON.toJSONString(obj) // 仅用于日志时不需要mock
// 编码解码 - 直接使用
Base64.getEncoder().encode(bytes)
URLEncoder.encode(str, "UTF-8")
// 常量获取 - 直接使用
ResourceType.INSTANCE.getName() // 枚举常量
Constants.MAX_RETRY_COUNT // 常量值
判断原则:
- ✅ 需要Mock:有副作用、依赖外部状态、需要控制返回值
- ❌ 不需Mock:纯函数、无副作用、结果可预测、性能开销小
7.2 Mockito 3.x配置要点
java
// 1. 依赖版本匹配
// Mockito 3.11.2 + mockito-inline (当前项目配置)
// 2. 注解使用
@RunWith(MockitoJUnitRunner.class) // 使用Mockito的Runner
// 3. 静态Mock的生命周期管理
public class ServiceTest {
private MockedStatic<StaticClass> staticMock;
@Before
public void setUp() {
MockitoAnnotations.openMocks(this); // 初始化普通mock
staticMock = mockStatic(StaticClass.class); // 创建静态mock
}
@After
public void tearDown() {
if (staticMock != null) {
staticMock.close(); // 必须关闭,避免影响其他测试
}
}
}
7.3 性能优化建议
减少静态Mock使用以提升测试速度:
java
// ❌ 错误:过度使用静态Mock
@Before
public void setUp() {
stringUtilsMock = mockStatic(StringUtils.class); // 不需要
collectionsMock = mockStatic(Collections.class); // 不需要
jsonMock = mockStatic(JSON.class); // 如果只是日志,不需要
mathMock = mockStatic(Math.class); // 不需要
businessServiceMock = mockStatic(BusinessService.class); // 必要
}
// ✅ 正确:最小化静态Mock使用
@Before
public void setUp() {
businessServiceMock = mockStatic(BusinessService.class); // 只mock必要的业务类
}
测试性能对比(Mockito 3.x):
- 不使用静态Mock: ~30ms/test
- 使用MockedStatic(1个类): ~80ms/test
- 使用MockedStatic(3个类): ~150ms/test
- 使用MockedStatic(5+类): ~300ms/test
7.4 常见问题解决
问题 | 原因 | 解决方案 |
---|---|---|
MockedStatic泄漏 | 未正确关闭 | 在@After中调用close() |
静态mock不生效 | MockedStatic已关闭 | 确保在测试期间保持打开 |
并发测试失败 | 静态mock冲突 | 避免并行执行使用静态mock的测试 |
测试运行缓慢 | 过度使用静态Mock | 只mock必要的静态方法 |
内存泄漏 | MockedStatic未释放 | 使用try-with-resources或@After关闭 |
7.5 从PowerMock迁移到Mockito 3.x
迁移步骤:
-
更新依赖:
- 移除
powermock-module-junit4
和powermock-api-mockito2
- 添加
mockito-inline
(版本已在父pom管理)
- 移除
-
代码迁移对照:
java
// PowerMock写法
@RunWith(PowerMockRunner.class)
@PrepareForTest({StaticClass.class})
public class OldTest {
@Before
public void setUp() {
PowerMockito.mockStatic(StaticClass.class);
when(StaticClass.method()).thenReturn(value);
}
}
// Mockito 3.x写法
@RunWith(MockitoJUnitRunner.class)
public class NewTest {
private MockedStatic<StaticClass> staticMock;
@Before
public void setUp() {
staticMock = mockStatic(StaticClass.class);
staticMock.when(StaticClass::method).thenReturn(value);
}
@After
public void tearDown() {
if (staticMock != null) staticMock.close();
}
}
测试类别 | 测试场景 | 具体描述 |
---|---|---|
异常处理 | 异常传播 | 异常包装和转换 |
逻辑分支 | if/else | 所有条件分支 |
switch | 所有case分支+default | |
集合操作 | 空集合 | 空列表、空Map、空Set |
单元素 | 只有一个元素的集合 | |
多元素 | 正常多元素场景 |
七、总结
🎯 成功标准
-
覆盖率达标
- ✅ 行覆盖率 > 80%
- ✅ 分支覆盖率 > 60%
-
测试质量
- ✅ 所有测试独立运行
- ✅ Mock所有外部依赖
- ✅ Mock间接依赖(内部方法的外部调用)
- ✅ 覆盖所有边界条件
- ✅ 异常场景完整测试
-
代码规范
- ✅ 命名规范清晰
- ✅ 测试结构清晰
- ✅ 断言明确有效
⚠️ 重要提醒
测试失败时的关键检查点:
- 检查所有直接依赖是否已mock
- 特别注意内部方法调用的外部依赖
- 检查ThreadLocal等上下文是否初始化
- 分析完整的方法调用链
- NPE通常指向未mock的外部调用
- UnnecessaryStubbingException通常表示测试间状态冲突或Mock未被使用