JaCoCo是面向Java的开源代码覆盖率工具,JaCoCo以Java代理模式运行,它负责在运行测试时检测字节码。 JaCoCo会深入研究每个指令,并显示每个测试过程中要执行的行。 为了收集覆盖率数据,JaCoCo使用ASM即时进行代码检测,并在此过程中从JVM Tool Interface接收事件,最终生成代码覆盖率报告。
jacoco运行有离线(offline)、在线(on the fly)模式之说,所谓在线模式就是在应用启动时加入jacoco agent进行插桩,在开发、测试人员使用应用期间实时地进行代码覆盖率分析。相信很多的java项目开发人员并不会去写单元测试代码的,因此覆盖率统计就要把手工测试或接口测试覆盖的情况作为重要依据,显然在线模式更符合实际需求,本文以在线模式为例进行演示。
从官网
https://www.jacoco.org/jacoco/下载最新版本并解压到指定目录
覆盖率是用来衡量测试代码对功能代码的测试情况。通过统计测试代码中对功能代码中行、分支、类等模拟场景数量,来量化说明测试的充分度。
代码覆盖率=代码的覆盖程度,一种度量方式。
覆盖率简单说:跑了一个测试用例,项目代码中的那些模块、文件、类、方法 执行了。
其中行覆盖率是最细颗粒度,其他覆盖率都可从行覆盖率情况统计算出来。
当至少一个指令被指定源码行执行时,该源码行被认为已执行。
通俗的说就是,测试行为,触发了这某一行代码,则这一行代码就被覆盖了。
if 和switch 语句算作分支覆盖率,这个指标计算一个方法中的分支总数,并决定已执行和未执行的分支的数量。
全部未覆盖:所有分支均未执行,红色标志
部分覆盖:部分分支被执行,黄色标志
全覆盖:所有分支均已执行,绿色标志。
当方法中至少有一个指令被执行,该方法被认为已执行,包括构造函数和静态初始化方法。
若代码如下:
if (i>100)
j = 10/i // 没有除0错误
else
j = 10/(i+2) // i==-2,除0错误
覆盖2个分支,只需要设计i101 和 i1,但是对于找到i==-2这个bug点时没有作用的。
所以:
1、不要简单的追求高的代码覆盖率
2、高覆盖率测试用例不等于测试用例有效
3、没覆盖的分支相当于该分支上的任何错误肯定都找不到
分析未覆盖部分的代码,反推测试设计是否充分,没有覆盖到的代码是否存在测试设计盲点。
Instructions: Java 字节指令的覆盖率。执行的最小单位,和代码的格式无关。
Branches: 分支覆盖率。注意,异常处理不算做分支。
Cxty(Cyclomatic Complexity): 圈复杂度, Jacoco 会为每一个非抽象方法计算圈复杂度,并为类,包以及组(groups)计算复杂度。
圈复杂度简单的说就是为了覆盖所有路径,所需要执行单元测试数量,圈复杂度大说明程序代码可能质量低且难于测试和维护。
Lines: 行覆盖率,只要本行有一条指令被执行,则本行则被标记为被执行。
Methods: 方法覆盖率,任何非抽象的方法,只要有一条指令被执行,则该方法被计为被执行。
Classes: 类覆盖率,所有类,包括接口,只要其中有一个方法被执行,则标记为被执行。注意:构造函数和静态初始化块也算作方法。
类角度相信信息
钻石代表分支覆盖情况、背景颜色代表指令覆盖率
红色钻石/背景:这一行没有分支/指令被执行
黄色钻石/背景:这一行中只有部分分支/指令被执行
绿色钻石/背景:这一行的所有分支/指令都被执行
jacoco使用插桩的方式来记录覆盖率数据,是通过一个probe探针来注入。jacoco有2种插桩模式
,offline模式和on-the-fly模式。
对应的是jacoco的offline模式。
offline模式就是在测试之前先对文件进行插桩,生成插过桩的class或jar包,测试插过桩的class和jar包,生成覆盖率信息到文件,最后统一处理,生成报告。
在脱机模式下,可以使用与代理可用的相同财产集配置JaCoCo运行时,除了类文件已经插入指令的include/excludes选项之外。有两种不同的方式来提供配置:
配置文件:如果在类路径选项中提供了文件jacoco-agent.properties,则从该文件加载。文件必须格式化为Java配置文件格式。
系统配置:选项也可以作为Java系统配置提供。在这种情况下,选项必须以“jacoco-agent”为前缀。例如,*.exec文件的位置可以用系统属性“jacoco-angent.destfile”配置。
<project>
<reporting>
<plugins>
<plugin>
<groupId>org.jacocogroupId>
<artifactId>jacoco-maven-pluginartifactId>
<reportSets>
<reportSet>
<reports>
<report>reportreport>
reports>
reportSet>
reportSets>
plugin>
plugins>
reporting>
project>
离线调用jacoco有两种方式,第一种是命令行方式调用jacoco的插件,另外一种是直接在maven中配置jacoco插件。
之前项目中曾经调研过其实还有第三种方式,我是在maven调用是开启了jacoco的代理在maven参数中,官方没有写这样用的方式所以不建议。
在执行mvn命令时,加上“org.jacoco:jacoco-maven-plugin:prepare-agent”参数即可。 示例:
mvn clean test org.jacoco:jacoco-maven-plugin:0.7.3.201502191951:prepare-agent install -Dmaven.test.failure.ignore=true
具体的配置方法如下:
1.添加依賴
org.jacoco
jacoco-maven-plugin
0.8.3
2.配置plugins
<plugin>
<groupId>org.jacocogroupId>
<artifactId>jacoco-maven-pluginartifactId>
<version>0.8.3version>
<configuration>
<includes>
<include>com/**/*include>
includes>
configuration>
<executions>
<execution>
<id>pre-testid>
<goals>
<goal>prepare-agentgoal>
goals>
execution>
<execution>
<id>post-testid>
<phase>testphase>
<goals>
<goal>reportgoal>
goals>
execution>
executions>
plugin>
其中包含(includes)或排除(excludes)字段的值应该是相对于目录/ classes /的编译类的类路径(而不是包名),使用标准通配符语法:
* Match zero or more characters
** Match zero or more directories
? Match a single character
你也可以这样排除一个包和它的所有子包/子包:
com/src/**/*
这将排除某些包装中的每个课程,以及任何孩子。例如,com.src.child也不会包含在报表中。
也可以在pom中指定筛选规则:
<plugin>
<groupId>org.jacocogroupId>
<artifactId>jacoco-maven-pluginartifactId>
<version>${jacoco.version}version>
<configuration>
<includes>
<include>com/src/**/*include>
includes>
<rules>
<rule implementation="org.jacoco.maven.RuleConfiguration">
<element>BUNDLEelement>
<limits>
<limit implementation="org.jacoco.report.check.Limit">
<counter>METHODcounter>
<value>COVEREDRATIOvalue>
<minimum>0.50minimum>
limit>
<limit implementation="org.jacoco.report.check.Limit">
<counter>BRANCHcounter>
<value>COVEREDRATIOvalue>
<minimum>0.50minimum>
limit>
<limit implementation="org.jacoco.report.check.Limit">
<counter>CLASScounter>
<value>MISSEDCOUNTvalue>
<maximum>0maximum>
limit>
limits>
rule>
rules>
configuration>
<executions>
<execution>
<id>pre-testid>
<goals>
<goal>prepare-agentgoal>
goals>
execution>
<execution>
<id>post-testid>
<phase>testphase>
<goals>
<goal>reportgoal>
goals>
execution>
executions>
plugin>
此时运行mvn test生成index.html(即覆盖率报告)位置在:
也可以指定输出目录:
<execution>
<id>post-unit-testid>
<phase>testphase>
<goals>
<goal>reportgoal>
goals>
<configuration>
<dataFile>target/jacoco.execdataFile>
<outputDirectory>target/jacoco-utoutputDirectory>
configuration>
execution>
对应的是jacoco的on-the-fly模式。
JVM通过 -javaagent参数指定jar文件启动代理程序,代理程序在ClassLoader装载一个class前判断是否修改class文件,并将探针插入class文件,探针不改变原有方法的行为,只是记录是否已经执行。
打开cmd,cd到示例代码的target目录,执行如下命令:
基本语法
-javaagent:[yourpath/]jacocoagent.jar=[option1]=[value1],[option2]=[value2]
示例
java -
javaagent:jacocoagent.jar=includes=*,output=tcpserver,port=6300,address=localhost,append=true -jar
demo.jar
关键参数说明:
基于参数
java -jar jacococli.jar dump [--address ] --destfile [--help] [--port ] [--quiet] [--reset] [--retry ]
Option | Description |
---|---|
--address |
要连接的主机名或ip地址(默认本地主机) |
--destfile |
要将执行数据写入的文件 |
--help |
show help |
--port |
要连接的端口(默认值为6300) |
--quiet |
禁止stdout上的所有输出 |
--reset |
转储后重置测试目标上的执行数据 |
--retry |
重试次数(默认为10) |
java -jar jacococli.jar dump --address 127.0.0.1 --port 6300 --destfile ./jacoco-demo.exec
基本参数
java -jar jacococli.jar report [ ...] --classfiles [--csv ] [--encoding ] [--help] [--html ] [--name ] [--quiet] [--sourcefiles ] [--tabwith ] [--xml ]
java -jar jacococli.jar report jacoco-demo.exec --classfiles ../../../target/classes --sourcefiles ../../main/java --html http-report --xml report.xml --encoding=utf-8
注意 jacococli.jar 是放到了test下的resource下上述命令。
Option | Description |
---|---|
|
要读取的JaCoCo*.exec文件列表 |
--classfiles |
Java类文件的位置 |
--csv |
CSV报告的输出文件 |
--encoding |
源文件编码(默认使用平台编码) |
--help |
show help |
--html |
HTML报表的输出目录 |
--name |
用于此报告的名称 |
--quiet |
禁止stdout上的所有输出 |
--sourcefiles |
源文件的位置 ,既源码位置 |
基本参数
java -jar jacococli.jar instrument [ ...] --dest [--help] [--quiet]
Option | Description |
---|---|
|
源文件的位置 ,既源码位置 |
--dest |
将插入指令的Java类写入的路径 |
--help |
show help |
--quiet |
suppress all output on stdout |
基本参数
java -jar jacococli.jar merge [ ...] --destfile [--help] [--quiet]
Option | Description |
---|---|
|
要读取的JaCoCo*.exec文件列表 |
--destfile |
将合并的执行数据写入的文件 |
--help |
show help |
--quiet |
suppress all output on stdout |
这个merge参数是讲多个exec整合为一个文件,据我使用经验来说有两方面用途吧,一是讲多个模块的覆盖率信息统计到一起,二是将n次相同模块的测试结果整合统计。都很有用。
示例
java -jar jacococli.jar merge jacoco-demo.exec jacoco-demo1.exec --destfile ./demo.exec
java -jar jacococli.jar classinfo [ ...] [--help] [--quiet] [--verbose]
Option | Description |
---|---|
|
Java类文件的位置 |
--help |
show help |
--quiet |
suppress all output on stdout |
--verbose |
show method and line number details |
java -jar jacococli.jar execinfo [ ...] [--help] [--quiet]
Option | Description |
---|---|
|
list of JaCoCo *.exec files to read |
--help |
show help |
--quiet |
suppress all output on stdout |