代码覆盖率工具 JaCoCo
JaCoCo 是面向 Java 的开源代码覆盖率工具,JaCoCo 以 Java 代理模式运行,它负责在运行测试时检测字节码。JaCoCo 会深入研究每个指令,并显示每个测试过程中要执行的行。为了收集覆盖率数据,JaCoCo 使用 ASM 即时进行代码检测,并在此过程中从 JVM Tool Interface 接收事件,最终生成代码覆盖率报告。
JaCoCo 运行有 离线(offline
)、在线(on the fly
)模式之说,所谓在线模式就是在应用启动时加入 jacoco agent
进行 插桩,在开发、测试人员使用应用期间实时地进行代码覆盖率分析。相信很多的 Java 项目开发人员并不会去写单元测试代码的,因此覆盖率统计就要把手工测试或接口测试覆盖的情况作为重要依据,显然在线模式更符合实际需求,本文以在线模式为例进行演示。
1.Jacoco 安装
从官网下载最新版本并解压到指定目录。
主要用到如下两个 jar
包:
在线模式 Jacoco 覆盖率统计包含三个步骤:
- 启动应用增加
jacoco agent
进行插桩,该步骤会启动 TCP Server。 - 从 TCP Server
dump
生成代码覆盖率文件,生成的文件格式为.exec
格式的二进制文件。 - 解析
.exec
格式文件生成html
格式代码覆盖率报告。
2.准备 Java 示例代码
事先需要创建 Java 示例代码,本文不聚焦 Java 代码相关的内容,大家可以使用自己的 Java 项目代码进行验证,或者直接 clone
本文提供的 Spring Boot 示例代码仓库:https://gitee.com/pepperpapa/jacoco_demo。
注意:在线模式使用的代码一定要是类似 Web 应用这种永远不会退出的程序为例,因为收集
jacoco agent
要启用 TCP Server,如果程序执行完就退出了 TCP Server 也就无法关闭了。
示例代码就是提供两个简单的 URL 访问,访问截图如下:
http://127.0.0.1:8080/hello
http://127.0.0.1:8080/byebye
执行构建命令 mvn clean package
后,会在 target
目录生成可运行的 jar
包。
3.启动 jacoco agent 进行插桩
打开 cmd
,cd
到示例代码的 target
目录,执行如下命令:
java -javaagent:C:\工具\jacoco-0.8.6\lib\jacocoagent.jar=includes=*,output=tcpserver,port=6300,address=localhost,append=true -jar demo-0.0.1-SNAPSHOT.jar
关键参数说明:
-javaagent:C:\工具\jacoco-0.8.6\lib\jacocoagent.jar=includes=*
,这个参数就是启用 Jacoco 代理参数,其中C:\工具\jacoco-0.8.6\lib\jacocoagent.jar
就是之前下载 Jacoco 解压后的jacocoagent.jar
的绝对路径,includes
表示对要插桩的包进行过滤,*
代表所有的class
都要进行插桩,也可以根据情况进行过滤,如includes=com.mycompany.*
。output=tcpserver
,这里不需要改动,表示以 TCP Server 方式启动应用并插桩。port=6300
,Jacoco 开启的 TCP Server 的端口,不能被占用。address=localhost
,对外开放的地址,也可以指定 IP 地址。demo-0.0.1-SNAPSHOT.jar
,就是示例代码构建后target
目录生成的jar
包,需要根据实际情况更新。
执行后就会启动 Web 服务,截图如下:
特别提醒:为了对比实时统计代码覆盖率的效果,此时请先不要请求上面提到的任何一个 URL,切记!
4.dump 生成覆盖率文件
保持服务启动,再打开一个 cmd
窗口同样 cd
到 target
目录,执行如下命令从上一步开启的 TCP Server 中 dump
出覆盖率文件:
java -jar C:\工具\jacoco-0.8.6\lib\jacococli.jar dump --address localhost --port 6300 --destfile ./jacoco_tcp_01.exec
参数说明:
--destfile ./jacoco_tcp.exec
,其中./jacoco_tcp.exec
为生成exec
文件名,表示在当前目录生成。- 其他参数和上一步类似,不再特别说明,注意需要更新
jacococli.jar
的绝对路径。
执行成功后,会在 target
目录生成相应的 .exec
文件。
接下来,我们在浏览器中访问该web示例应用提供的两个 URL,http://127.0.0.1:8080/hello
、http://127.0.0.1:8080/byebye
,应用会调用相应的方法并正常返回结果。
然后,我们再次执行 dump
命令再生成一个 .exec
文件,命名为 jacoco_tcp_02.exec
。
java -jar C:\工具\jacoco-0.8.6\lib\jacococli.jar dump --address localhost --port 6300 --destfile ./jacoco_tcp_02.exec
5.分析 exec 文件生成 html 报告
dump
命令生成的 .exec
文件为二进制文件,需要进行解析,以生成 html
报告为例,执行如下的命令分别将两个 .exec
文件解析成 html
报告。
java -jar C:\工具\jacoco-0.8.6\lib\jacococli.jar report ./jacoco_tcp_01.exec --classfiles .\classes --sourcefiles ..\src\main\java --html report01
java -jar C:\工具\jacoco-0.8.6\lib\jacococli.jar report ./jacoco_tcp_02.exec --classfiles .\classes --sourcefiles ..\src\main\java --html report02
参数说明:
./jacoco_tcp_01.exec
,表示要解析的exec
文件的相对路径。--classfiles .\classes
,需要指定生成的classes
文件目录,参见下图。--sourcefiles ..\src\main\java
,需要指定源码的文件目录,参见下图。
最终,target
目录会生成 report01
、report02
两个目录,分别打开其中的 index.html
查看代码覆盖率结果。
我们发现 report01
中的代码覆盖率为
73
%
73\%
73%,其中 say()
和 bybye()
两个方法没有被覆盖:
而我们访问了对应的服务之后生成的 report02
覆盖率报告为 100%,say()
和 bybye()
这两个方法都覆盖到了:
6.覆盖率指标说明
Instructions
:Java 字节 指令的覆盖率。执行的最小单位,和代码的格式无关。Branches
:分支覆盖率。注意,异常处理不算做分支。Cxty
(Cyclomatic Complexity
):圈复杂度,Jacoco 会为每一个非抽象方法计算圈复杂度,并为类、包以及组(groups
)计算复杂度。圈复杂度简单地说就是为了覆盖所有路径,所需要执行单元测试数量,圈复杂度大说明程序代码可能质量低且难于测试和维护。Lines
:行覆盖率,只要本行有一条指令被执行,则本行则被标记为被执行。Methods
:方法覆盖率,任何非抽象的方法,只要有一条指令被执行,则该方法就会被计为被执行。Classes
:类覆盖率,所有类,包括接口,只要其中有一个方法被执行,则标记为被执行。注意:构造函数和静态初始化块也算作方法。