jacoco运行时动态统计覆盖率
Jacoco介绍
jacoco的前身为emma,在06年的时候emma团队发布了最后一个版本后变宣布停止维护emma。并另起一个项目jacoco来继续开展代码覆盖
率的工作。
jacoco原理
再启动任何java程序之前,jacoco都会动态的将2进制字节码插桩进入我们的应用程序。并启动一个tcp的服务监控代码覆盖变化。用户可以实
时的动态dump出覆盖率数据以生成报告。
jacoco各项覆盖率指标
覆盖率计数器
Instructions(指令覆盖率)(C0 Coverage)
Jacoco最小的计数单元是单个java二进制代码指令。指令覆盖率提供了代码是否被执行的信息。这个度量完全独立源码格式,并且总是可用,
即使class文件里面没有调试信息。
Branches(分支覆盖率)(C1 Coverage)
Jacoco也计算分支的覆盖率,包括所有的if和switch语句。这个度量计算一个方法里面的总分支数,确定执行和不执行的分支数量。分支覆盖
率总是可用的,即使class文件里面没有调试信息。注意异常处理是不在分支度量里面统计的。
如果class文件使用调试信息编译的话,产生的覆盖率可以映射到源码行并且高亮提示:
没有覆盖:在这一行中没有分支被执行(红色方块)
部分覆盖:这一行的分支中只有一部分被执行(黄色方块)
完全覆盖:这一行的所有分支都被执行(绿色方块)
Cyclomatic Complexity(圈复杂度)
Jacoco同样可以为每一个非抽象方法计算复杂度,最终计算出类、包和组的复杂度。根据由McCabe1996圈复杂度的定义是,在(线性)组合
中,计算在一个方法里面所有可能路径的最小数目。所以复杂度可以作为度量单元测试是否有完全覆盖所有场景的一个依据。复杂度即使是在
没有调试信息的情况下也可以计算。
圈复杂度V(G)的正式定义是基于方法的控制流图的有向图表示:
v(G) = E – N + 2
E是边界的数量,N是节点的数量。Jacoco 基于下面的方程来计算复杂度,B是分支的数量,D是决策点的数量:
v(G) = B – D + 1
基于每个分支的被覆盖情况,Jacoco也为每个方法计算覆盖和缺失的复杂度。缺失的复杂度同样表示测试案例没有完全覆盖到这个模块。注意
Jacoco不将异常处理作为分支,try/catch块也同样不增加复杂度。
lines(行覆盖率)
所有的class文件使用debug信息编译之后,就可以计算行的覆盖率信息。一行源代码是否被执行,要看这一行中是否至少有一个指令被执行。
由于实际上一行代码一般被编译成多个二进制代码指令,这样源码在高亮显示时,会显示成3种不同的状态:
没有覆盖:这一行中没有指令被执行(红色背景)
部分覆盖:这一行中只有一部分指令被执行(黄色背景)
完全覆盖:这一行中所有指令都被覆盖(绿色背景
methods(方法覆盖率)
每一个非抽象方法至少包含一个指令。一个方法是否执行取决于方法中是否有至少一个指令被执行。在Jacoco中,构造器和静态初始化同样会
像方法一样统计。其中一些方法可能没有可以直接对应的源码,比如默认构造器或常量的初始化命令。
1.
2.
1.
2.
3.
像方法一样统计。其中一些方法可能没有可以直接对应的源码,比如默认构造器或常量的初始化命令。
classes(类覆盖率)
一个方法是否执行取决于类中是否有至少一个方法被执行。注意Jacoco认为构造器和静态初始化都是方法。Java的接口一般包含静态初始化,
所以接口也同样被认为是可执行的类。
具体使用
首先我们需要两个jar包。jacocoagent和jacocoant。所有关于jacoco的包都可以去 下载http://www.jacoco.org/jacoco/
jacocoagent:运行时启动tcp服务监控代码覆盖,dump出覆盖率数据
jacocoant:jacoco的任务是ant驱动的。所以这个包用来执行jacoco的任务,向tcp服务发送请求。
以先知2.0的simba模块为例。我们通过mvn package打出了一个jar包,我们看看启动服务的命令。
nohup java -javaagent:/root/jacocoagent.jar=output=tcpserver,port=8893,address=${local_ip} -jar
simba-1.0.jar --spring.config.name=application-prod --prophet.mariadb.host=${mariadb_ip}
--rpc.workermanager.host=${tm_ip} --rpc.taskscheduler.host=${tm_ip} hdfs.FSHost=172.27.2.11
--hdfs.FSPort=8020 --hdfs.FSUser=hdp >simba.log 2>&1 &
可以看到启动java的时候我们使用-javaagent这个参数指定了jcocoagent.jar为代理。后面跟了几个参数
output:输出覆盖率数据的方式,tcpserver的意思就是开启一个tcp服务,动态监控覆盖率数据。用这种方式可以不停止服务就能动
态dump覆盖率数据。否则就需要kill掉服务才能统计覆盖率了
port:tcp服务的端口号
address:tcp服务的ip地址,一般是本机地址
这样启动服务之后我们就动态的监控代码覆盖的变化了。这个时候就到了jacocoant出马的时候。定义一个build.xml文件定义ant任务。它会
向tcp发送dump和生成report的请求。我们看一下具体的定义
<?xml version="1.0" ?>
<project name="Lengyu" xmlns:jacoco="antlib:org.jacoco.ant" default="jacoco">
<!--Jacoco-->
<property name="jacocoantPath" value="/root/files/jacocoant.jar"/>
<!--.execJacoco-->
<property name="jacocoexecPath" value="/opt/web/simba/file/jacoco.exec"/>
<!---->
<property name="reportfolderPath" value="/opt/web/simba/coverage/"/>
<!--ip-->
<property name="server_ip" value="172.27.1.216"/>
<!--jacocoagent-->
<property name="server_port" value="8893"/>
<!---->
<property name="checkOrderSrcpath" value="/opt/web/simba/src/main/java/" />
<!--.class-->
<property name="checkOrderClasspath" value="/opt/web/simba/target/classes" />
<!--antJacoco-->
<taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml">
<classpath path="${jacocoantPath}" />
</taskdef>
<!--dump:
ip
tomcat.exec-->
<target name="dump">
<jacoco:dump address="${server_ip}" reset="false" destfile="${jacocoexecPath}"
port="${server_port}" append="true"/>
</target>
<!--jacoco:
.class
dump.exechtml-->
<target name="report">
<delete dir="${reportfolderPath}" />
<mkdir dir="${reportfolderPath}" />
<jacoco:report>
<executiondata>
<file file="${jacocoexecPath}" />
</executiondata>
<structure name="JaCoCo Report">
<group name="Check Order related">
<classfiles>
<fileset dir="${checkOrderClasspath}" />
</classfiles>
<sourcefiles encoding="utf-8">
<fileset dir="${checkOrderSrcpath}" />
</sourcefiles>
</group>
</structure>
<html destdir="${reportfolderPath}" encoding="utf-8" />
</jacoco:report>
</target>
</project>
我们运行 ant dump就会dump出exec文件,里面有覆盖率的数据。然后运行ant
report,就会根据exec生成相应的覆盖率报告。注意build.xml的配置一定要正确。尤其是exec的路径和jacoco的jar包路径,tcp服务的ip和p
ort
更多的使用方式,请参考官方文档:http://www.eclemma.org/jacoco/trunk/doc/