腾讯移动品质中心TMQ [腾讯 TMQ] JAVA 代码覆盖率工具 JaCoCo-原理篇

匿名 · 2016年08月23日 · 最后由 恩里科 回复于 2017年11月09日 · 3351 次阅读

作者:刘洋

一、 覆盖率定义

作为一个测试人员,保证产品的软件质量是其工作首要目标,为了这个目标,测试人员常常会通过很多手段或工具来加以保证,覆盖率就是其中一环比较重要的环节。

我们通常会将测试覆盖率分为两个部分,即 “需求覆盖率” 和 “代码覆盖率”。

需求覆盖:指的是测试人员对需求的了解程度,根据需求的可测试性来拆分成各个子需求点,来编写相应的测试用例,最终建立一个需求和用例的映射关系,以用例的测试结果来验证需求的实现,可以理解为黑盒覆盖。

代码覆盖:为了更加全面的覆盖,我们可能还需要理解被测程序的逻辑,需要考虑到每个函数的输入与输出,逻辑分支代码的执行情况,这个时候我们的测试执行情况就以代码覆盖率来衡量,可以理解为白盒覆盖。

以上两者完全可以相辅相成,用代码覆盖结果反向的检查需求覆盖 (用例) 的测试是否充分完整。

如果做覆盖率测试?我们可以借助一些网上流行的各种覆盖率工具,本章主要介绍 JaCoCo 这个工具。

二、JAVA 覆盖率工具介绍

市场上 java 主要代码覆盖率工具:EMMA、JaCoCo

总结一下个人对 JaCoCo 优势的理解:

(1)JaCoCo 支持分支覆盖、引入了 Agent 模式。

(2)EMMA 官网已经不维护了,JaCoCo 是其团队开发的,可以理解为一个升级版。

(3)JaCoCo 社区比较活跃,官网也在不断的维护更新。

我们前期使用的 EMMA,也做了全量、差异覆盖率,和精准耦合也结合在了一起,但后来考虑到 JaCoCo 的优势,也就全部切换了过来。

2.1 JaCoCo 简述

JaCoCo 是一个开源的覆盖率工具 (官网地址:http://www.eclemma.org/JaCoCo/),它针对的开发语言是 java,其使用方法很灵活,可以嵌入到 Ant、Maven 中;可以作为 Eclipse 插件,可以使用其 JavaAgent 技术监控 Java 程序等等。

很多第三方的工具提供了对 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 方式

参见 http://eclemma.org/jacoco/trunk/doc/ant.html

主要有以下几种,具体使用就不介绍了,应用宝是用的这种方式,后续有介绍。

Task coverage、Task agent、Task dump、Task merge、Task report、Task instrument

3.2 命令行方式

参见 http://www.eclemma.org/jacoco/trunk/doc/agent.html

使用方式说明:

主要放在 JAVA_OPTS 中,比如:

由 AgentOptions 的 getVMArgument 方法加载,各参数入 AgentOptions 的对应参数,为后续操作做为输入。

下面是官网的所有参数说明:

系统在 jvm 停止的时候会 dump 覆盖率信息。

关键的核心代码在这里,Agent.java 在有一段代码

Runtime.getRuntime().addShutdownHook 这个方法的意思就是在 jvm 中增加一个关闭的钩子,当 jvm 关闭的时候,会执行系统中已经设置的所有通过方法 addShutdownHook 添加的钩子,当系统执行完这些钩子后,jvm 才会关闭。所以这些钩子可以在 jvm 关闭的时候进行内存清理、对象销毁等操作。

也就是在 JVM 关闭的时候调用 agent.shutdown(),也就是写覆盖率数据。

###3.3 Apache Maven 方式
参见 http://www.eclemma.org/jacoco/trunk/doc/maven.html

这种方式适合 Maven 的项目。

下面简单说下调用方式原理:

就拿官方的 Offline Example 来说吧,其内容如下:

注意蓝色的部分,上面的配置主要做了以下几个事情:

(1)项目已 jar 包方式打包,引入 junit 和 jacoco。

(2)Build 时执行 instrument、report、check。

(3)覆盖率生成到 target/jacoco.exec

我们看看他是怎么触发调用的。

在 jacoco 源码中:jacoco-maven-plugin\target\classes\META-INF\maven\org.jacoco\jacoco-maven-plugin 目录下有个 plugin-help.xml 文件,它里面标明了具体的调用方式。

截出 instrument 这段,关键地方就是下面蓝色部分。

官网上关于参数的说明:

给出一个整理后的表格:

再给一个 jacoco 的 maven 部分的代码目录:

到这里,大家应该清楚其调用的方式了吧。

###3.4 Eclipse EclDmma Plugin 方式

具体步骤如下:

(1)在 Eclipse 菜单中选择 Help → Install New Software...

(2)在安装弹框中输入http://update.eclemma.org/,勾选出现的版本。

(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 在实际业务中的使用情况,踩坑篇里面包含了几个当时遇到的比较棘手的问题的解决思路,有兴趣的童鞋请关注。

本章完~

原文链接:http://tmq.qq.com/2016/08/java-code-coverage-tools-jacoco-principle/


TMQ(腾讯移动品质中心)是腾讯最早专注在移动 APP 测试的团队
我们专注于移动测试技术精华,饱含腾讯多款亿级 APP 的品质秘密,文章皆独家原创,我们不谈虚的,只谈干货!

扫码关注我们

扫一扫 关注 TMQ
精彩分享不断
共收到 5 条回复 时间 点赞

javaCOCO 是什么鬼

写的是有点深的,不过又是 Agent...这些年

弱弱说一句。。。2.3 的第 2 节 里面应该是 jdk1.5 ,或者说是 java 5

项目中增量代码覆盖率如何统计?

wsg 回复

JavaCodeCoverage-JaCoCo,爪哇岛上的可可。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册