JaCoCo-测试可度量化
作者:互联网
最近为了响应公司要求,大家都在积极(无奈)的补单元测试,要求单元测试行覆盖率达到*%,在之前的概念中,并没有了解过如何统计我们单元测试的行覆盖率,这次接触到JaCoCo也让我非常惊喜,它可以帮我们非常直观的看到单元测试的代码层面的覆盖情况,不过这个仅针对Java代码,大家酌情选择,下面和大家一起分享下吧。
一、JaCoCo的使用
1.引入插件
在pom.xml中引入我们的JaCoCo插件,如下:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.2</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
插件引入成功后就可以在idea的maven中看到对应的JaCoCo插件:
2.运行JaCoCo插件
在我们的test文件夹下有测试方法的前提下运行JaCoCo插件或者执行maven命令:mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent package -Dmaven.test.failure.ignore=true org.jacoco:jacoco-maven-plugin:report surefire-report:report就可以在项目target文件夹下生成对应的文件如下:
在项目目录\target\site\jacoco目录下打开index文件就可以看到每个类的覆盖率情况
其中红色的为测试未覆盖(未执行)到的代码,绿色的为已覆盖到的代码。
至此我们项目引入JaCoCo就已经结束了,是不是很简单呢,实际上我觉得我好的插件就应该这样简单上手,毕竟越好用,学习成本越低一个东西推广的才能更快。下面的话就简单介绍下JaCoCo的工作原理吧。
二、覆盖率计数器
1. 行覆盖
所有类文件均携带debug信息编译,则每行的覆盖率可计算。当至少一个指令被指定到源码行且已执行时,该源码行被认为已执行。
**全部未覆盖:该行中指令均未执行,红色标志
**部分覆盖:该行中部分指令执行,黄色标志
**全覆盖:该行中所有指令已执行,绿色标志
2. 类覆盖
当类中至少有一个方法已执行,则该类被认为已执行。Jacoco中认为构造函数和静态初始化方法也当作被执行过的方法。Java接口类型若包含静态初始化方法,这种接口也被认为是可执行的类。
3. 方法覆盖
每个非抽象方法至少包含一个指令。当至少一个指令被执行,该方法被认为已执行。由于Jacoco基于字节码级别的,构造函数和静态初始化方法也被当作方法计算。其中有些方法,可能无法直接对应到源码中,比如默认构造器或常量的初始化命令。
4. 分支覆盖
Jacoco为if和switch语句计算分支覆盖率。这个指标计算一个方法中的分支总数,并决定已执行和未执行的分支的数量。分支覆盖率在class文件中缺少debug信息时也可使用。异常处理不在分支覆盖的统计范围内。
**全部未覆盖:所有分支均未执行,红色标志
**部分覆盖:只有部分分支被执行,黄色标志
**全覆盖:所有分支均已执行,绿色标志
5. 指令覆盖
Jacoco计数的最小单元是Java字节码指令,它为执行/未执行代码提供了大量的信息。这个指标完全独立于源格式,在类文件中缺少debug信息时也可以使用。
6. 圈复杂度
Jacoco对每个非抽象方法计算圈复杂度,总结类、包、组的复杂性。
圈复杂度:在(线性)组合中,计算在一个方法里面所有可能路径的最小数目。所以复杂度可以作为度量单元测试是否有完全覆盖所有场景的一个依据。在没有debug信息的时候也可以使用。
**圈复杂度V(G)是基于方法的控制流图的有向图表示:V(G) = E - N + 2
**E是边界数量,N是节点数量。
**Jacoco基于下面方程来计算复杂度,B是分支数量,D是决策点数量:
**V(G) = B - D + 1
基于每个分支的被覆盖情况,Jacoco也未每个方法计算覆盖和缺失的复杂度。缺失复杂度同样表示测试案例没有完全覆盖到这个模块。注意Jacoco不将异常处理作为分支,try/catch块也同样不增加复杂度。
三、Jacoco原理
Jacoco使用插桩的方式来记录覆盖率数据,是通过一个probe探针来注入。
插桩模式有两种:
1. on-the-fly模式
JVM通过 -javaagent参数指定jar文件启动代理程序,代理程序在ClassLoader装载一个class前判断是否修改class文件,并将探针插入class文件,探针不改变原有方法的行为,只是记录是否已经执行。
2. offline模式
在测试之前先对文件进行插桩,生成插过桩的class或jar包,测试插过桩的class和jar包,生成覆盖率信息到文件,最后统一处理,生成报告。
on-the-fly和offline对比
on-the-fly更方便简单,无需提前插桩,无需考虑classpath设置问题。
以下情况不适合使用on-the-fly模式:
(1)不支持javaagent
(2)无法设置JVM参数
(3)字节码需要被转换成其他虚拟机
(4)动态修改字节码过程和其他agent冲突
(5)无法自定义用户加载类
Java方法的控制流分析
官方文档在这里:https://www.jacoco.org/jacoco/trunk/doc/flow.html
1. 探针插入策略
探针可以在现有指令之间插入附加指令,他们不改变已有方法行为,只是去记录是否已经执行。可以认为探针放置在控制流图的边缘上,理论上讲,我们可以在控制流图的每个边缘插入一个探针,但这样会增加类文件大小,降低执行速度。事实上,我们每个方法只需要一些探针,具体取决于方法的控制流程。
如果已经执行了探测,我们知道已经访问了相应的边缘,从这个边缘我们可以得出其他前面的节点和边:
(1)如果访问了边,我们知道该边的源节点已经被执行。
(2)如果节点已经被执行且节点是一个边缘的目标节点,则我们知道已经访问了该边。
上述探针插入策略没有考虑到隐式异常,如果两个探针之间的控制流被未使用throw的语句显示创建的异常终端,则其间的所有指令都被视为未覆盖。因此,只要后续行包含至少一个方法调用,Jacoco就会在两行的指令间添加额外的探测。该方法仅使用于有debug信息的编译的类文件。且不考虑除方法调用之外的其他指令的隐式异常。
2. 探针的实现
探针需要满足如下几点要求:
(1)记录执行
(2)识别不同的探针
(3)线程安全
(4)对应用程序无影响
(5)最小的运行时开销
Jacoco给每个类一个boolean[]数组实例,每个探针对应该数组中的一个条目。无论何时执行,都用下面4条字节码指令将条目设置为true。
ALOAD probearray
xPUSH probeid
ICONST_1
BASTORE
四、参考文献
非常感谢网上分享自己文章的小伙伴,下面是我参考的文章:
idea 使用jacoco_浅谈Jacoco原理_weixin_39747577的博客-CSDN博客
单元测试和集成测试_zhang_xiaoyi的博客-CSDN博客_单元测试和集成测试
标签:jacoco,覆盖,探针,Jacoco,指令,测试,可度量化,执行,JaCoCo 来源: https://blog.csdn.net/WX5991/article/details/121807187