JAVA代码覆盖率⼯具JaCoCo-原理篇
关于JAVA代码覆盖率⼯具JaCoCo,作者会通过三篇来介绍,分别为原理篇、实践篇和踩坑篇,先从原理篇开始介绍~
⼀、覆盖率定义
作为⼀个测试⼈员,保证产品的软件质量是其⼯作⾸要⽬标,为了这个⽬标,测试⼈员常常会通过很多⼿段或⼯具来加以保证,覆盖率就是其中⼀环⽐较重要的环节。
我们通常会将测试覆盖率分为两个部分,即“需求覆盖率”和“代码覆盖率”。
需求覆盖:指的是测试⼈员对需求的了解程度,根据需求的可测试性来拆分成各个⼦需求点,来编写相应的测试⽤例,最终建⽴⼀个需求和⽤例的映射关系,以⽤例的测试结果来验证需求的实现,可以理解为⿊盒覆盖。
代码覆盖:为了更加全⾯的覆盖,我们可能还需要理解被测程序的逻辑,需要考虑到每个函数的输⼊与输出,逻辑分⽀代码的执⾏情况,这个时候我们的测试执⾏情况就以代码覆盖率来衡量,可以理解为⽩盒覆盖。
以上两者完全可以相辅相成,⽤代码覆盖结果反向的检查需求覆盖(⽤例)的测试是否充分完整。
如果做覆盖率测试?我们可以借助⼀些⽹上流⾏的各种覆盖率⼯具,本章主要介绍JaCoCo这个⼯具。
⼆、JAVA覆盖率⼯具介绍
市场上java主要代码覆盖率⼯具:EMMA、JaCoCo。
总结⼀下个⼈对JaCoCo优势的理解:
(1) JaCoCo⽀持分⽀覆盖、引⼊了Agent模式。
(2) EMMA官⽹已经不维护了,JaCoCo是其团队开发的,可以理解为⼀个升级版。
(3) JaCoCo社区⽐较活跃,官⽹也在不断的维护更新。
我们前期使⽤的EMMA,也做了全量、差异覆盖率,和精准耦合也结合在了⼀起,但后来考虑到JaCoCo的优势,也就全部切换了过来。
2.1 JaCoCo简述
很多第三⽅的⼯具提供了对JaCoCo的集成,如sonar、Jenkins等。
JaCoCo包含了多种尺度的覆盖率计数器,包含指令级覆盖(Instructions,C0coverage),分⽀(Branches,C1coverage)、圈复杂度(CyclomaticComplexity)、⾏覆盖(Lines)、⽅法覆盖(non-abstract methods)、类覆盖(classes),后⾯会⼀⼀介绍。
我们先看看其覆盖率结果展现如下图1-1所⽰,⽅便读者先有⼀个⼤概的了解。
图1-1 覆盖率报告结果部分截图
标⽰绿⾊的为⾏覆盖充分,标红⾊的为未覆盖的⾏,黄⾊菱形的为分⽀部分覆盖,绿⾊菱形为分⽀完全覆盖。
通过这个报告的结果就可以知道代码真实的执⾏情况,便于我们分析评估结果。
2.2 JaCoCo基本概念
⾏覆盖率:度量被测程序的每⾏代码是否被执⾏,判断标准⾏中是否⾄少有⼀个指令被执⾏。
类覆盖率:度量计算class类⽂件是否被执⾏。
分⽀覆盖率:度量if和switch语句的分⽀覆盖情况,计算⼀个⽅法⾥⾯的总分⽀数,确定执⾏和不执⾏的分⽀数量。
⽅法覆盖率:度量被测程序的⽅法执⾏情况,是否执⾏取决于⽅法中是否有⾄少⼀个指令被执⾏。
指令覆盖:计数单元是单个java⼆进制代码指令,指令覆盖率提供了代码是否被执⾏的信息,度量完全独⽴源码格式。
圈复杂度:在(线性)组合中,计算在⼀个⽅法⾥⾯所有可能路径的最⼩数⽬,缺失的复杂度同样表⽰测试案例没有完全覆盖到这个模块。
2.3 JaCoCo 原理
1、注⼊⽅式介绍
这个图包含了⼏种不同的收集覆盖率信息的⽅法,每种⽅法的实现⽅法都不⼀样,带颜⾊的部分是JaCoCo⽐较有特⾊的地⽅。
上⾯各个名次含义(带颜⾊的为JaCoCo⽀持):
上表JaCoCo⽀持的部分,再详细的解释下:
(1) JaCoCo在Byte Code时使⽤的ASM技术修改字节码⽅法,可以修改Jar⽂件、class⽂件字节码⽂件。
(2) JaCoCo同时⽀持on-the-fly和offline的两种插桩模式。
On-the-fly插桩:
JVM中通过-javaagent参数指定特定的jar⽂件启动Instrumentation的代理程序,代理程序在通过Class Loader装载⼀个class前判断是否转换修改class⽂件,将统计代码插⼊class,测试覆盖率分析可以在JVM执⾏测试代码的过程中完成。
Offline模式:
在测试前先对⽂件进⾏插桩,然后⽣成插过桩的class或jar包,测试插过桩的class和jar包后,会⽣成动态覆盖信息到⽂件,最后统⼀对覆盖信息进⾏处理,并⽣成报告。
On-the-fly和offline⽐较:
On-the-fly模式更⽅便简单进⾏代码覆盖分析,⽆需提前进⾏字节码插桩,⽆需考虑classpath 的设置。
存在如下情况不适合on-the-fly,需要采⽤offline提前对字节码插桩:
(1) 运⾏环境不⽀持java agent。
(2) 部署环境不允许设置JVM参数。
(3) 字节码需要被转换成其他的虚拟机如Android Dalvik VM。
(4) 动态修改字节码过程中和其他agent冲突。
(5) ⽆法⾃定义⽤户加载类。
2、JaCoCo执⾏最⼩的java版本
最⼩需要Java1.5
3、字节码处理⽅式
JaCoCo通过注⼊来修改和⽣成java字节码,使⽤的是ASM库。
4、java⽅法控制流分析
JaCoCo是如何在字节码注⼊的?
我们带着疑问来看下⾯的内容:
先举个实例,有个java⽅法:
编译后转换成字节码后,内容如下:
我们知道JaCoCo是字节码注⼊⽅式,它是通过⼀个Probe探针的⽅式来注⼊的,具体如下:
探针是字节指令集插⼊到java⽅法中,程序执⾏后可以被记录,它不会改变原有代码的⾏为。
我们看看探针前后插⼊⽐较:
颜⾊的部分就是探针注⼊的地⽅。
JaCoCo是根据控制流Type来采⽤不同的探针插⼊策略的。
⼀个⽤java字节码定义的java⽅法的控制流图可能有以下的type,每⼀个type连接⼀个源指令与⽬标指令,type不同探针的注⼊策略也会不同,如下是type定义:
探针不改变该⽅法的⾏为,但记录他们已被执⾏的事实,从理论上讲,可以在控制流图的每⼀个边插⼊⼀个探针,作为探针实现本⾝需要多个字节码指令,这将增加⼏倍的类⽂件的⼤⼩和执⾏速度。
事实上,只需要⼀个⼏个探头,根据每个⽅法的控制流的⽅法,下⾯说明了如何在不同的边缘类型的情
况下添加额外的指令:
⼀个instrumented class可以⽤以下代码检索其探针数组实例:
JaCoCo是⽤⼀个布尔数组来实现探针,每个探针对应于该数组中的项。当以下四个字节码指令触发时探针进⾏输⼊设置为true:
JaCoCo对⾏探针是这样处理的,添加两⾏指令之间的⼀个额外的探针时,后续⾏⾄少包含⼀个⽅法调⽤。
以上是JaCoCo插桩原理,如果想深⼊了解,可以去看看它的源码实现。
三、JaCoCo使⽤⽅式
使⽤⽅式有很多,这⾥贴出了相应的参考链接,根据项⽬的不同可以灵活供有需要的读者去学习。
3.1 Apache Ant⽅式
主要有以下⼏种,具体使⽤就不介绍了,应⽤宝是⽤的这种⽅式,后续有介绍。
Task coverage、Task agent、Task dump、Task merge、Task report、Task instrument
3.2 命令⾏⽅式
使⽤⽅式说明:
java源码阅读工具主要放在JAVA_OPTS中,⽐如:
由AgentOptions的getVMArgument⽅法加载,各参数⼊AgentOptions的对应参数,为后续操作做为输⼊。
下⾯是官⽹的所有参数说明:
系统在jvm停⽌的时候会dump覆盖率信息。
关键的核⼼代码在这⾥,Agent.java在有⼀段代码
也就是在JVM关闭的时候调⽤agent.shutdown(),也就是写覆盖率数据。
3.3 Apache Maven⽅式
这种⽅式适合Maven的项⽬。
下⾯简单说下调⽤⽅式原理:
就拿官⽅的Offline Example来说吧,其部分内容如下:
注意蓝⾊的部分,上⾯的配置主要做了以下⼏个事情:
(1) 项⽬已jar包⽅式打包,引⼊junit和jacoco。
(2) Build时执⾏instrument、report、check。
(3) 覆盖率⽣成到
我们看看他是怎么触发调⽤的。
在jacoco源码中:jacoco-maven-plugin\target\classes\META-INF\maven\org.jacoco\jacoco-maven-plugin⽬录下有个l⽂件,它⾥⾯标明了具体的调⽤⽅式。
截出instrument这段,关键地⽅就是下⾯蓝⾊部分。
官⽹上关于参数的说明:
给出⼀个整理后的表格:
再给⼀个jacoco的maven部分的代码⽬录:
到这⾥,⼤家应该清楚其调⽤的⽅式了吧。
3.4 Eclipse EclDmma Plugin⽅式
具体步骤如下:
(1) 在Eclipse菜单中选择Help → Install
(3) 核对版本,点击Next。
(4) 根据向导完成安装。
(5) 使⽤就不说了。
3.5 与Jekins集成
(1) 先要在jenkins上安装JaCoCo的插件,安装完成之后在job的配置项中可以增加这个选项(如图1-2):
图1-2
(2) 选择后出现(图1-3):
图1-3
第⼀个录⼊框是你的覆盖率⽂件(exec),第⼆个是class⽂件⽬录,第三个是源代码⽂件⽬录。
(3) 配置好了之后进⾏构建,构建完成之后job⾸页就会出现覆盖率的趋势图(图1-4),⿏标点击趋势图可以看到覆盖率详情(图1-5),包括具体覆盖率数据和源码的覆盖率情况:
图1-4 趋势图
图1-5 覆盖率详情
未完待续 :
JaCoCo原理篇就介绍到这⾥了,后续还有项⽬实践篇和踩坑篇,实践篇主要介绍下JaCoCo在实际业务中的使⽤情况,踩坑篇⾥⾯包含了⼏个当时遇到的⽐较棘⼿的问题的解决思路,有兴趣的童鞋请关注。