Jacoco的一种基本用法和两种高阶用法,它通过javaage
Jacoco的一种基本用法和两种高阶用法,它通过javaage
简介
JaCoCo(Java Code Coverage)是一个Java代码覆盖率工具,用于分析单元测试或集成测试对代码的覆盖情况。它通过java agent 技术统计测试过程中执行的代码行、分支、方法等,帮助开发者评估测试的有效性并发现未被覆盖的代码区域。它一般被用来做检查单元测试的代码覆盖率,除此之外,在实践中我还总结出了两种高阶用法。
前置知识
Jacoco 支持的覆盖率类型
- 行覆盖率(Line Coverage)
- 含义:代码中每行是否被至少执行一次。
- 计算方式:
已覆盖的行数 / 总行数
- 示例:若一个方法有 10 行代码,其中 8 行被执行过,则行覆盖率为 80%。
- 分支覆盖率(Branch Coverage)
- 含义:每个条件语句(如
if
、switch
)的分支是否都被覆盖。
- 计算方式:
已覆盖的分支数 / 总分支数
- 示例:
if (a && b)
会产生 4 种分支组合(TT, TF, FT, FF),若只覆盖了 TT 和 TF,则分支覆盖率为 50%。
- 方法覆盖率(Method Coverage)
- 含义:类中的每个方法是否被至少调用一次。
- 计算方式:
已覆盖的方法数 / 总方法数
- 示例:一个类有 5 个方法,其中 3 个被调用过,则方法覆盖率为 60%。
- 类覆盖率(Class Coverage)
- 含义:项目中的每个类是否被至少加载一次。
- 计算方式:
已覆盖的类数 / 总类数
- 示例:项目有 20 个类,其中 15 个被加载过,则类覆盖率为 75%。
- 指令覆盖率(Instruction Coverage)
- 含义:字节码指令的执行情况(JaCoCo 基于字节码插桩)。
- 计算方式:
已覆盖的指令数 / 总指令数
- 用途:更细粒度的覆盖分析,通常开发者较少直接关注。
- 圈复杂度覆盖率(Cyclomatic Complexity Coverage)
- 含义:基于代码的圈复杂度(条件分支复杂度)计算覆盖率。
- 用途:帮助识别代码中复杂且未被覆盖的逻辑路径。
Jacoco的调用方式
已覆盖的行数 / 总行数
if
、switch
)的分支是否都被覆盖。已覆盖的分支数 / 总分支数
if (a && b)
会产生 4 种分支组合(TT, TF, FT, FF),若只覆盖了 TT 和 TF,则分支覆盖率为 50%。已覆盖的方法数 / 总方法数
已覆盖的类数 / 总类数
已覆盖的指令数 / 总指令数
有两种调用jacoco的方式:
- 一是使用 maven插件。这个适用于单元测试场景。
- 二是直接使用二进制包。这个适用于迭代测试场景、老项目重构场景。
二进制包下载地址:https://www.eclemma.org/jacoco/index.html
它里采用最新版本的, 0.8.12。
解压二进制包,找到 lib/jacocoagent.jar。
兼容性及注意事项
通过反编译jacocoagent.jar中的类文件,我们发现它是使用jdk5编译的,这个保证它可以支持 jdk5及以上
。
经过测试,jacoco 0.8.12在 jdk8和jdk17这两个LTS版本下可以正常运行。
注意事项:jacoco为了保证兼容性,使用的是jdk5编译,这导致它在解析中文路径时会出错,要知道jdk直到jdk8才解决了中文路径问题。因此,不要在中文路径下使用jacoco,jacoco参数中也不要出现中文路径。
基本用法:检查单元测试覆盖率
- 在src/test 下编写测试用例
- 在pom.xml中配置如下:
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<id>default-prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>default-report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<id>default-prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>default-report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
上面的配置有两个关键点,
- 目标(goal) prepare-agent是注入 jacoco-agent.jar,如下:
- 目标(goal)report则是生成报告,它会利用 jacoco.exec数据文件来生成 site文件。
执行: mvn clean test
, 可以在target下看到生成了jacoco.exec,以及 /site/jacoco目录。如下图所示:
用浏览器打开 /site/jacoco/index.html,可以看到详细的测试报告,如下图所示:
高阶用法一:在迭代测试中验证测试团队的用例的覆盖率
痛点
测试团队使用的是黑盒测试,测试完成之后,不知道哪些代码有被覆盖到(执行到),哪些没有被覆盖到(执行到),可能会出现遗漏的情况。
解决方案
通过在java程序启动时,加入jacoco-agent,可以采集测试期间被【测试人员】执行到的代码。通过分析【代码覆盖数据文件】 jacoco.exec,可以查看到哪些代码有被执行,哪些代码没有被执行。
java -javaagent:jacocoagent.jar -jar app.jar
测试用例及验证
构建一个最简的springboot web应用,暴露两个接口;
http://localhost:8080/test1
http://localhost:8080/test2
编译程序,然后使用 -javaagent:jacocoagent.jar 参数来启动程序。
在程序运行起来之后,访问以下url:
http://localhost:8080/test2
验证
-
关闭java应用,可以看到目录下生成了一个名为jacoco.exec的文件。
-
在
IDEA
中加载这个文件。 点击:菜单Run > Show Coverage Data
。
-
点击 + 号,选择之前复制过来的 .exec文件,然后点击 Show selected。
可以看到 TestController下共有3个方法,但只有两个被覆盖到。绿色表示有被执行,红色表示没有被执行。
高阶用法二:老项目重构场景下找出无用的代码
痛点
部门有许多5年+的项目,这些项目在经历组织架构变更、功能变更之后,很多代码都没有用了。 在项目中大家都知道有很多代码没用,但不确定是哪些,不敢删,结果无用的代码越聚越多,严重影响开发效率。新人加入项目,要在一堆无用的代码中找到有用的代码。
解决方案
- 通过在java程序启动时,加入jacoco-agent,可以采集到代码上线期间被【用户】执行的代码。
java -javaagent:jacocoagent.jar -jar app.jar
- 让程序运行1个月,即采集1个月的使用数据。
- 分析【代码覆盖数据文件】 jacoco.exec(方法同高阶用法一),可以查看到哪些代码有被执行,哪些代码没有被执行。以下是覆盖率统计的示例:
如何删除无用的代码
- 保险起见,不要直接删除,可以先注释掉,等过一段时间(比如1-2个月)再删除代码。
- 优先删除整个类没有被使用的,接着是没有被使用的方法。方法内某些没有被使用的代码行就没有必要管了。
- 清理之后,上线一个新版本,再收集一波数据,再看一下行覆盖率情况,然后再处理。依次循环,直到覆盖率数据达到比较理想的情况,比如90%。
- 在采集【代码覆盖数据】如果觉得1个月不够,可以再延长1-2个月。1个功能如果3个月没有用,那么这个功能大概率是没人用了。
用户点评