一、定位未覆盖的代码
-
利用 IDEA 的覆盖率工具:
- 右键测试类 → Run with Coverage,或使用
Alt+Shift+F10
(Windows)打开运行菜单选择覆盖率。 - 查看高亮标记:
- 绿色:已覆盖代码行。
- 红色:未覆盖代码行。
- 黄色:部分覆盖(如条件分支未完全覆盖)。
- 右键测试类 → Run with Coverage,或使用
-
分析 JaCoCo 报告:
- 打开
target/site/jacoco/index.html
,查看:- 行覆盖率(Line):哪些行未执行?
- 分支覆盖率(Branch):哪些条件分支(如
if/else
)未覆盖? - 方法覆盖率:是否有未调用的方法?
- 打开
二、针对性提升覆盖率的策略
策略 1:覆盖边界条件
- 示例场景:一个计算器类的方法
divide(int a, int b)
。public int divide(int a, int b) { if (b == 0) throw new IllegalArgumentException("除数不能为0"); return a / b; }
- 问题:常规测试可能只覆盖
b≠0
的情况,遗漏了异常分支。 - 解决方案:
@Test void testDivideByZero() { Calculator calculator = new Calculator(); assertThrows(IllegalArgumentException.class, () -> calculator.divide(5, 0)); }
策略 2:覆盖所有代码分支
- 示例场景:带有
if-else
的逻辑。public String getGrade(int score) { if (score >= 90) return "A"; else if (score >= 60) return "B"; else return "C"; }
- 问题:若仅测试
score=80
,则未覆盖score≥90
和score<60
的分支。 - 解决方案:使用参数化测试覆盖所有分支:
@ParameterizedTest @CsvSource({"95, A", "75, B", "50, C"}) void testGetGrade(int score, String expected) { assertEquals(expected, grader.getGrade(score)); }
策略 3:覆盖异常和错误处理
- 示例场景:数据库操作失败时的回滚逻辑。
public void saveData(Data data) { try { database.insert(data); } catch (SQLException e) { logger.error("保存失败", e); rollback(); } }
- 问题:正常流程测试不会触发
catch
块。 - 解决方案:使用 Mockito 模拟异常:
@Test void testSaveDataFailure() { Database mockDb = mock(Database.class); when(mockDb.insert(any())).thenThrow(new SQLException()); DataService service = new DataService(mockDb); service.saveData(new Data()); verify(mockDb).rollback(); // 验证是否执行了回滚 }
策略 4:覆盖工具生成的代码
- 常见问题:Lombok 生成的
getter/setter
、equals/hashCode
或 IDE 自动生成的代码未覆盖。 - 解决方案:
- 显式测试生成的代码(如验证
equals
方法)。 - 配置 JaCoCo 忽略 Lombok 生成的代码(在
pom.xml
中):<configuration> <excludes> <exclude>**/*$Lombok*/**</exclude> </excludes> </configuration>
- 显式测试生成的代码(如验证
三、高级技巧
技巧 1:强制覆盖难以触发的代码
- 场景:测试
private
方法或静态代码块。public class ConfigLoader { static { loadConfig(); // 静态代码块 } private static void loadConfig() { /* 加载配置 */ } }
- 解决方案:通过反射调用私有方法或触发静态初始化:
@Test void testStaticBlock() throws Exception { Class.forName("com.example.ConfigLoader"); // 触发静态代码块 }
技巧 2:优化测试数据
- 使用随机测试工具:如
QuickTheories
或jqwik
,生成大量随机输入覆盖边缘情况。@Property void testRandomInput(@ForAll int a, @ForAll int b) { assumeTrue(b != 0); // 忽略 b=0 的情况 assertEquals(a / b, calculator.divide(a, b)); }
技巧 3:忽略无需覆盖的代码
- 配置 JaCoCo 排除(在
pom.xml
中):<excludes> <exclude>**/model/*.java</exclude> // 忽略 POJO 类 <exclude>**/Main.java</exclude> // 忽略启动类 </excludes>
四、避免常见误区
-
盲目追求 100% 覆盖率:
- 某些代码(如自动生成的代码、简单 Getter)无需强制覆盖。
- 更关注核心逻辑和复杂分支的覆盖。
-
编写无效测试:
@Test void testAdd() { calculator.add(2, 3); // 没有断言!看似覆盖,实则无效 }
-
忽略测试代码质量:
- 避免重复代码:用
@BeforeEach
初始化公共对象。 - 遵循命名规范:测试方法名应明确表达场景(如
testDivide_WhenDivisorIsZero_ThrowException
)。
- 避免重复代码:用
五、总结
通过以下步骤系统提升覆盖率:
- 定位未覆盖代码:使用 IDEA 高亮和 JaCoCo 报告。
- 设计针对性用例:覆盖边界条件、异常分支、复杂逻辑。
- 利用工具和技巧:参数化测试、Mock 异常、反射调用。
- 平衡覆盖率和成本:优先覆盖关键代码,忽略无关部分。