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