一、代码覆盖率统计工具的能与不能
能:代码覆盖率统计工具能用来发现没有被测试(单元测试、接口自动化测试、ui自动化测试、手工测试等)覆盖的代码。
1、测试中未覆盖的代码可能存在风险:通过分析未覆盖的代码,反推在测试用例设计、测试脚本设计过程中的疏漏,从中找出隐藏的bug。
2、发现测试死角、冗余代码、历史废弃代码:可以发现多个测试用例都覆盖不到的代码。收集方法覆盖率,为废弃的代码提供依据。
3、度量自动化用例:为自动化(单元、接口、ui)测试用例提供覆盖率统计情况,完善自动化测试用例。
4、精准回归:构建代码调用关系,精准的确定回归测试范围,避免全量回归造成测试资源的浪费。
不能:代码覆盖率统计不能完全用来衡量代码质量
100%覆盖的代码并不意味着100%无bug的应用。代码覆盖率作为一个指导性指标,可以一定程度上反应测试的完备程度,是软件质量度量的一种手段。
二、覆盖率计数器
2.1、行覆盖
行覆盖又叫语句覆盖,就是度量被测代码中每个可执行语句是否被执行到了。这里说的是“可执行语句”,因此就不会包括像C++的头文件声明,代码注释,空行,等等。非常好理解,只统计能够执行的代码被执行了多少行。需要注意的是,单独一行的花括号{}也常常被统计进去。语句覆盖常常被人指责为“最弱的覆盖”,它只管覆盖代码中的执行语句,却不考虑各种分支的组合等等。
**全部未覆盖:该行中指令均未执行,红色标志
**部分覆盖:该行中部分指令执行,黄色标志
**全覆盖:该行中所有指令已执行,绿色标志
2.2、类覆盖
当类中至少有一个方法已执行,则该类被认为已执行。
2.3、方法覆盖
执行到代码中的每一个非抽象方法(函数)。
2.4、分支覆盖
为if和switch语句计算分支覆盖率。这个指标计算一个方法中的分支总数,并决定已执行和未执行的分支的数量。分支覆盖率在class文件中缺少debug信息时也可使用。异常处理不在分支覆盖的统计范围内。
**全部未覆盖:所有分支均未执行,红色标志
**部分覆盖:只有部分分支被执行,黄色标志
**全覆盖:所有分支均已执行,绿色标志
2.5、指令覆盖
Java字节码指令是计数的最小单元,它为执行/未执行代码提供了大量的信息。这个指标完全独立于源格式,在类文件中缺少debug信息时也可以使用。
2.6、圈复杂度
在(线性)组合中,计算在一个方法里面所有可能路径的最小数目。所以复杂度可以作为度量单元测试是否有完全覆盖所有场景的一个依据。缺失的复杂度同样表示测试案例没有完全覆盖到这个模块。
三、插桩原理
3.1、On-the-fly插桩
java启动时添加 -javaagent 参数指定特定的jar文件启动代理程序,代理程序再通过自定义classloader实现自己的类装载策略,在类加载之前将探针插入class文件中。
3.2、Offline插装
在测试前先对文件进行插桩,然后生成插过桩的class或jar包,执行插过桩的class文件或者jar包之后,会生成覆盖率信息到文件,最后统一对覆盖率信息进行处理,并生成报告。
3.3、两种插装方式对比
On-The-Fly | Offline |
更加方便的获取代码覆盖率,无需提前进行字节码插桩,可以实时获取代码覆盖率信息 | 适用于以下场景:
|
四、JaCoCo的几种使用方式
JaCoCo的使用方式有很多,这里指贴出几种,根据项目的不同可以灵活使用。
4.1 Apache Ant方式
JaCoCo通过配置ant的build.xml,以启动具有执行记录的Java程序,并从记录的数据创建覆盖率报告。参见:http://eclemma.org/jacoco/trunk/doc/ant.html
主要有以下几种:Task coverage、Task agent、Task dump、Task merge、Task report、Task instrument
4.2、命令行方式
通过java命令行使用javaagent收集执行信息并根据请求或在JVM退出时将其转储。参见 http://www.eclemma.org/jacoco/trunk/doc/agent.html
有三种不同的执行数据输出模式:
- 文件系统:在JVM终止时,执行数据被写入本地文件。
- TCP套接字服务器:外部工具可以连接到JVM,并通过套接字连接检索执行数据。可以在VM退出时进行可选的执行数据重置和执行数据转储。
- TCP套接字客户端:启动时,JaCoCo代理连接到给定的TCP端点。执行数据根据请求写入套接字连接。可以在VM退出时进行可选的执行数据重置和执行数据转储。
使用方式说明:
-javaagent:[yourpath/]jacocoagent.jar=[option1]=[value1],[option2]=[value2]
举例:
JAVA_OPTS="-server -Xms2000m -Xmx2000m -Xmn800m -XX:PermSize=64m
-XX:MaxPermSize=256m -XX:SurvivorRatio=4 -XX:+UseConcMarkSweepGC
-XX:MaxTenuringThreshold=15 -Dfile.encoding=utf8 -Duser.language=zh
-javaagent:/home/auser/myproject/apache-tomcat-6.0.37/lib/
jacocoagent.jar=includes=com.xxx.*,output=tcpserver,port=8494,
address=10.10.10.10"
4.3、 Eclipse EclDmma Plugin方式
(1) 在Eclipse菜单中选择Help → Install New Software...
(2) 在安装弹框中输入http://update.eclemma.org/,勾选出现的版本。
(3) 核对版本,点击Next。
(4) 根据向导完成安装。
(5) 之后就是使用了。
4.4、与Jekins集成
(1) 先要在jenkins上安装JaCoCo的插件,安装完成之后在job的配置项中可以增加这个选项如下图
(2) 选择后如下图
第一个录入框是你的覆盖率文件(exec),第二个是class文件目录,第三个是源代码文件目录。
(3) 配置好了之后进行构建,构建完成之后job首页就会出现覆盖率的趋势图,鼠标点击趋势图可以看到覆盖率详情,包括具体覆盖率数据和源码的覆盖率情况:
趋势图
4.5 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部分的代码目录:
到这里,大家应该清楚其调用的方式了吧。
4.6、spring boot项目集成jacoco
1、生成一个spring boot项目,不知道可以百度有的是
2、将demo项目打包成jar包,运行jar包
java -jar demo-0.0.1-SNAPSHOT.jar
3、访问jacoco官网下载并解压最新包。
http://www.eclemma.org/jacoco/
4、下载ant并配置:https://ant.apache.org/bindownload.cgi(ant需配合环境变量)
下载解压缩并配置环境变量
5、进入ant/bin目录,新建build.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project name="test" xmlns:jacoco="antlib:org.jacoco.ant" >
<!--Jacoco的安装路径-->
<property name="jacocoantPath" value="D:\jacoco-0.8.3\lib\jacocoant.jar"/>
<!--最终生成.exec文件的路径,Jacoco就是根据这个文件生成最终的报告的-->
<property name="jacocoexecPath" value="D:\jacoco-0.8.3\target\jacoco.exec"/>
<!--生成覆盖率报告的路径-->
<property name="reportfolderPath" value="D:\jacoco-0.8.3\report"/>
<!--远程tomcat服务的ip地址-->
<property name="server_ip" value="127.0.0.1"/>
<!--前面配置的远程tomcat服务打开的端口,要跟上面配置的一样-->
<property name="server_port" value="6300"/>
<!--源代码路径可以包含多个源代码-->
<property name="webSrcpath" value="D:\springdemo\src\main\java" />
<!--.class文件路径可以包含多个-->
<property name="webClasspath" value="D:\springdemo\target\classes"/>
<!--让ant知道去哪儿找Jacoco-->
<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="true" destfile="${jacocoexecPath}" port="${server_port}" append="false"/>
</target>
<!--jacoco任务:
根据前面配置的源代码路径和.class文件路径,
根据dump后,生成的.exec文件,生成最终的html覆盖率报告。-->
<target name="report">
<delete dir="${reportfolderPath}" />
<mkdir dir="${reportfolderPath}" />
<jacoco:report>
<executiondata>
<file file="${jacocoexecPath}" />
</executiondata>
<structure name="JaCoCo Report">
<group name="Launch related">
<!--此处配置classes文件地址 -->
<classfiles>
<fileset dir="${webClasspath}" />
</classfiles>
<!--此处配置源码地址-->
<sourcefiles encoding="gbk">
<fileset dir="${webSrcpath}" />
</sourcefiles>
</group>
</structure>
<html destdir="${reportfolderPath}" encoding="utf-8" />
</jacoco:report>
</target>
</project>
注意:其中几个重要配置
jacocoexecPath:Jacoco的安装路径,这个很好理解就是Jacoco的包解压缩的位置,注意文档写的是window路径如果linux或mac相应修改
jacocoexecPath:最终生成.exec文件的路径,之后Jacoco就是根据这个文件生成最终的报告
reportfolderPath:生成报告的路径,html格式报告
server_ip:远程tomcat服务的ip地址或spring boot启动的服务器地址
server_port:服务器端口,跟启动tomcat或jar时的端口号相同即可
webSrcpath:源代码路径,这里就是demo的java文件地址
webClasspath:class文件路径,这里就是demo编译后的classes目录
6、启动spring boot jar包
java -javaagent:/Users/lrs/jacoco-0.8.5/lib/jacocoagent.jar=includes=*,output=tcpserver,port=6300,address=127.0.0.1 -jar /Users/lrs/jacocoDemo/target/demo-0.0.1-SNAPSHOT.jar
7、执行相应的手工测试用例
8、dump信息
cd /Users/lrs/apache-ant-1.9.15/bin
ant dump
9、生成报告
cd /Users/lrs/apache-ant-1.9.15/bin
ant report
10、查看报告内容
进入目录 /Users/lrs/jacoco-0.8.5/report 打开index.html,如下图:Missed是未覆盖的数量
注:如果出现乱码可能因为build.xml配置文件中字符集设置有问题,检查 sourcefiles encoding 和 destdir="${reportfolderPath}" encoding
五、覆盖率使用-简单介绍
5.1、产品提出需求
1、实现一个登录,输入用户名、密码,用户名密码匹配后登录系统,并显示用户信息;
2、用户名、密码不符登录失败给出错误提示“登录失败,用户名或密码错误!!!”;
3、用户名或密码必填;
5.2、测试设计用例
1、输入正确的用户名、密码登录系统,并显示用户信息;
2、用户输入错误的用户名密码;
~~~嗯嗯,用例完美了。怎么评判用例好坏呢,启动项目
5.3、启动项目
5.4、执行case,偷个懒直接写好了
第一个case:http://127.0.0.1:8080/login?name=lrs&passwd=code
第二个case:http://127.0.0.1:8080/login?name=lrs1&passwd=code
5.5、生成dump信息
5.6、生成报告
5.7、查看报告
看到有三个分支没有覆盖到,两个是非空判断,一个是if判断
5.8、补充设计用例
1、输入正确的用户名、密码登录系统,并显示用户信息;
2、用户输入错误的用户名密码;
3、输入用户名为空,提示用户名必填
4、输入密码为空,提示密码必填
5、输入用户名包含大小写
6、输入密码包含大小写
5.9、重新执行测试
第三个case:http://127.0.0.1:8080/login?name=&passwd=code
注:假设一次没有补充完整用例,可以通过合并报告显示到一起
5.10、最终版用例
第一个case:http://127.0.0.1:8080/login?name=lrs&passwd=code
第二个case:http://127.0.0.1:8080/login?name=lrs1&passwd=code
第三个case:http://127.0.0.1:8080/login?passwd=code
第四个case:http://127.0.0.1:8080/login?name=lrs
第五个case:http://127.0.0.1:8080/login?name=lRs&passwd=code
第六个case:http://127.0.0.1:8080/login?name=lrs&passwd=cOde
5.11、重新生成报告
注:遗留两个问题
1、log没有执行,原因是我没有重启服务器,重启服务后就会变绿
2、此句 if (name.equalsIgnoreCase("lrs") && passwd.equalsIgnoreCase("code")) 有分支没有覆盖到,原因是上边不允许用户名或密码为空。如果想看分支全覆盖那么改成或即可
我就不一一演示了,感兴趣可以自己试下~~~
参考文档:
https://www.bbsmax.com/A/Vx5MjPX7dN/
https://www.jianshu.com/p/a955d274dc9b
https://cloud.tencent.com/developer/article/1038055
https://cloud.tencent.com/developer/article/1038149