要提升代码测试覆盖率,必须覆盖try-catch-finally所有执行路径:①正常流程;②catch分支需显式触发异常(如mockito模拟或传入非法路径);③finally需在三种出口(正常、被捕获异常、未被捕获异常)下均验证执行;④多catch须按继承顺序声明并分别测试各异常类型;⑤优先用try-with-resources简化资源关闭覆盖。

要提升代码测试覆盖率,不能只盯着业务逻辑,try-catch-finally块本身也是必须覆盖的执行路径。很多团队单元测试通过了,但覆盖率卡在70%左右,问题常出在异常分支和finally逻辑没被触发。
catch分支必须显式触发
仅调用正常流程的方法,不会进入catch块。JUnit中需主动构造异常场景:
- 用Mockito模拟依赖抛出指定异常(如
when(service.doWork()).thenThrow(new IOException())) - 对文件操作类,传入一个真实不存在的路径,让
FileReader自然抛出FileNotFoundException - 避免“假覆盖”:不要用
try { ... } catch (Exception e) { }空处理来凑行数,这会让覆盖率数字虚高,实际没验证任何错误恢复逻辑
finally块的执行路径容易遗漏
finally看似“总会执行”,但测试时若没覆盖所有出口,JaCoCo等工具会标记为未覆盖:
- 需分别编写三个测试用例:① 正常执行无异常;② try中抛异常并被catch捕获;③ try中抛异常且未被捕获(向上抛出)——这三种情况下finally都应执行
- 特别注意return语句位置:如果try中有return,测试要验证finally是否真在返回前运行(可在finally里加日志或修改共享变量断言)
- 别忽略System.exit()这类终止场景,虽然测试中不常模拟,但需在代码审查中标记为“不可覆盖分支”
多catch顺序影响覆盖完整性
多个catch块不是并列关系,而是按声明顺序匹配。测试必须覆盖每种可能的匹配路径:
- 子类异常(如
SQLException)必须在父类(如Exception)之前声明,否则后者会提前拦截 - 每个catch块都要有对应测试:对同一段try代码,分别注入
SQLTimeoutException、SQLNonTransientException等不同子类,验证各自处理逻辑是否生效 - 兜底catch(如
catch (Exception e))不能只靠“默认情况”覆盖,要专门构造一个它能捕获但其他catch不能捕获的异常类型
用try-with-resources替代手动finally更易覆盖
传统finally中关闭资源的代码逻辑复杂(判空、嵌套try-catch),容易漏测。改用try-with-resources后:
- 资源声明即自动关闭,JVM保证close()被调用,无需手写finally逻辑
- 只需测试资源创建成功和失败两种主路径,close()方法本身的覆盖交给资源类的单元测试
- 若需验证关闭异常(如
AutoCloseable.close()抛异常),在try-with-resources外再包一层try-catch,并单独覆盖该分支
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











