最近研究Java覆盖率工具,了解到了jacoco,Cobertura这两款,但是Cobertura没有维护了,不支持新的java语法。下面简单介绍一下这两个工具的使用。
简介
- 市场上主要代码覆盖率工具:
- Emma
- Cobertura
- Jacoco
- Clover(商用)
具体见下表:
工具 | Jacoco | Emma | Cobertura |
原理 | 使用 ASM 修改字节码 | 修改 jar 文件,class 文件字节码文件 | 基于 jcoverage,基于 asm 框架对 class 文件插桩 |
覆盖粒度 | 行,类,方法,指令,分支 | 行,类,方法,基本块,指令,无分支覆盖 | 项目,包,类,方法的语句覆盖/分支覆盖 |
插桩 | on the fly、offline | on the fly、offline | offline,把统计代码插入编译好的class文件中 |
生成结果 | 在 Tomcat 的 catalina.sh 配置 javaangent 参数,指出需要收集覆盖率的文件,shutdown 时才收集,只能使用 kill 命令关闭 Tomcat,不要使用 kill -9 | html、xml、txt,二进制格式报表 | html,xml |
缺点 | 需要源代码 | 1、需要 debug 版本,并打来 build.xml 中的 debug 编译项; 2、需要源代码,且必须与插桩的代码完全一致 | 1、不能捕获测试用例中未考虑的异常; 2、关闭服务器才能输出覆盖率信息(已有修改源代码的解决方案,定时输出结果;输出结果之前设置了 hook,会与某些服务器的 hook 冲突,web 测试中需要将 cobertura.ser 文件来回 copy |
性能 | 快 | 小巧 | 插入的字节码信息更多 |
执行方式 | maven,ant,命令行 | 命令行 | maven,ant |
Jenkins 集成 | 生成 html 报告,直接与 hudson 集成,展示报告,无趋势图 | 无法与 hudson 集成 | 有集成的插件,美观的报告,有趋势图 |
报告实时性 | 默认关闭,可以动态从 jvm dump 出数据 | 可以不关闭服务器 | 默认是在关闭服务器时才写结果 |
维护状态 | 持续更新中 | 停止维护 | 停止维护,不支持java1.8的lamda表达式 |
考虑到方案实施的难度很大取决于工具是否仍保持维护更新,所以选择 jacoco 来进行实践。
Jacoco 是一个开源的覆盖率工具。Jacoco 可以嵌入到 Ant 、Maven 中,并提供了 EclEmma Eclipse 插件,也可以使用 Java Agent 技术监控 Java 程序。很多第三方的工具提供了对 Jacoco 的集成,如 sonar、Jenkins、IDEA。
Jacoco 包含了多种尺度的覆盖率计数器,包含指令级(Instructions,C0 coverage),分支(Branches,C1 coverage)、圈复杂度(Cyclomatic Complexity)、行(Lines)、方法(Non-abstract Methods)、类(Classes)。
➢ Instructions:Jacoco 计算的最小单位就是字节码指令。指令覆盖率表明了在所有的指令中,
哪些被指令过以及哪些没有被执行。这项指数完全独立于源码格式并且在任何情况下有效,不需要类文件
的调试信息。
➢ Branches:Jacoco 对所有的 if 和 switch 指令计算了分支覆盖率。这项指标会统计所有的分支数
量,并同时支出哪些分支被执行,哪些分支没有被执行。这项指标也在任何情况都有效。异常处理不考虑在分支范围内。
➢ Cyclomatic Complexity:Jacoco 为每个非抽象方法计算圈复杂度,并也会计算每个类,包,组的复杂度。根据 McCabe1996 的定义,圈复杂度可以理解为覆盖所有的可能情况最少使用的测试用
例数。这项参数也在任何情况下有效。
➢ Lines:该项指数在有调试信息的情况下计算。
➢ Methods:每一个非抽象方法都至少有一条指令。若一个方法至少被执行了一条指令,就认为它被执行过。因为 JaCoco 直接对字节码进行操作,所以有些方法没有在源码显示(比如某些构造方法和由编
译器自动生成的方法)也会被计入在内。
➢ Classes:每个类中只要有一个方法被执行,这个类就被认定为被执行。同 5 一样,有些没有在源码声明的方法被执行,也认定该类被执行。
1 JaCoCo
JaCoCo是一个开源的覆盖率工具,可以作为Eclipse、IDEA插件使用,也可以通过Maven插件的方式使用,还可以使用其JavaAgent,实时生成Java程序的覆盖率报告等等。
很多第三方的工具提供了对JaCoCo的集成,如sonar、Jenkins等。
1.1 单元测试覆盖率
1.1.1 Maven 属性
<properties>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 默认跳过测试 -->
<maven.test.skip>true</maven.test.skip>
<!-- 设置覆盖率报告位置,配置成属性的目的是便于命令行改变位置 -->
<jacoco.report.path>${project.build.directory}/coverage-reports/jacoco-ut</jacoco.report.path>
</properties>
1.1.2 插件配置
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.2</version>
<executions>
<!-- 准备指向Jacoco运行时Agent的属性,在测试执行之前传递给虚拟机参数 -->
<execution>
<id>pre-unit-test</id>
<goals>
<goal>prepare-agent</goal>
</goals>
<configuration>
<!-- 设置覆盖率数据文件路径 -->
<destFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</destFile>
</configuration>
</execution>
<!-- 确保在单元测试执行之后生成覆盖率报告 -->
<execution>
<id>post-unit-test</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<!-- 引用覆盖率文件的路径-->
<dataFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</dataFile>
<!-- 设置覆盖率报告存放路径. -->
<outputDirectory>${jacoco.report.path}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
1.1.3 Exclude class from JaCoCo coverage
Example:
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.5</version>
<configuration>
<excludes>
<!-- exclude classes in package com.acme.models whose name starts with Spring -->
<exclude>com/acme/models/Spring*</exclude>
<!-- exclude classes in package com.acme.api whose name ends with Api -->
<exclude>com/acme/api/*Api.class</exclude>
<!-- exclude all classes in package com.acme.generated -->
<exclude>com/acme/generated/**/*</exclude>
</excludes>
</configuration>
</plugin>
或者
the following as per the Jacoco docs
<configuration>
<excludes>
<exclude>**/*Config.*</exclude>
<exclude>**/*Dev.*</exclude>
</excludes>
</configuration>
The values of the exclude fields should be class paths (not package names) of the compiled classes relative to the directory target/classes/ using the standard wildcard syntax
* Match zero or more characters
** Match zero or more directories
? Match a single character
You may also exclude a package and all of its children/subpackages this way:
<exclude>some/package/**/*</exclude>
1.2 生成报告
mvn clean test -Dmaven.test.skip=false
生成 其他目录下面
mvn clean test -Dmaven.test.skip=false -Djacoco.report.path=/opt/websuite/nginx/html
1.3 接口测试覆盖率
通过在JVM启动参数中加入-javaagent
参数指定 JaCoCo 的代理程序,在Class Loader装载一个class前将统计代码插入class文件,达到在执行测试代码或者人工功能测试的时候,实时统计覆盖率的目的。
1.3.1 下载 Agent
下载地址: https://github.com/jacoco/jacoco/releases,下载之后解压,找到 lib/jacocoagent.jar 文件
1.3.2 配置 Agent
|
- javaagent : jacocoagent.jar 文件的的全路径
- includes: 为需要分析的 包
- output: 输出覆盖率报告数据的方式,其它还有
- address : 本机IP
- port: 暴露的端口
详见官方文档: https://www.eclemma.org/jacoco/trunk/doc/agent.html
1.3.3 dump报告数据
找到 lib/jacococli.jar
|
1.3.4 生成 html 覆盖率报告
|
1.4 其他
- JVM 启动 java agent 参数中加上 jmx 配置,可通过 MBean 的方式操作 dump 数据
- 官方有提供 可通过代码导出生成报告的示例,可封装成 HTTP 接口 进行管理
1.5 与其他工具对比
原理 | 使用 ASM 修改字节码 | 修改 jar 文件,class 文件字节码文件 | 基于 jcoverage,基于 asm 框架对 class 文件插桩 |
覆盖粒度 | 行,类,方法,指令,分支 | 行,类,方法,基本块,指令,无分支覆盖 | 项目,包,类,方法的语句覆盖/分支覆盖 |
插桩 | on the fly、offline | on the fly、offline | offline,把统计代码插入编译好的class文件中 |
生成结果 | exec 数据文件 | html、xml、txt,二进制格式报表 | html,xml |
缺点 | 需要源代码 | 1、需要 debug 版本,并打来 build.xml 中的 debug 编译项; 2、需要源代码,且必须与插桩的代码完全一致 | 1、不能捕获测试用例中未考虑的异常; 2、关闭服务器才能输出覆盖率信息 |
性能 | 快 | 小巧 | 插入的字节码信息更多 |
执行方式 | maven,ant,命令行Sonar、Jenkins、IDE | 命令行 | maven,ant |
Jenkins 集成 | 生成 html 报告,展示报告,无趋势图 | 无法与 hudson 集成 | 有集成的插件,美观的报告,有趋势图 |
报告实时性 | 默认关闭,可以动态从 jvm dump 出数据 | 可以不关闭服务器 | 默认是在关闭服务器时才写结果 |
维护状态 | 持续更新中 | 停止维护 | 停止维护 |
以上对比结果来自: Jacoco Code Coverage
1.6 idea中使用JaCoCo
1.7 Jacoco+Maven+Jenkins配置
可参考:
https://www.jianshu.com/p/e7fc806ea0e0
2 Cobertura
命令:$ mvn cobertura:cobertura
效果:打开项目目录:target/site/cobertura/下的index.xml文件,就能看到测试覆盖率报告:
mvn cobertura:help 查看cobertura插件的帮助
mvn cobertura:clean 清理插件生产的中间及最终报告文件
mvn cobertura:check 检查最后一次标注(instrumentation) 正确与否
mvn cobertura:cobertura 运行cobertura的检查任务并生成报表,报表生成在target/site/cobertura目录下
cobertura:dump-datafile Cobertura 数据文件 dump 指令 , 不常用
mvn cobertura:instrument 标注编译好的 javaclass 文件
2.1 Cobertura 的 pom.xml配置
参考:
https://www.mojohaus.org/cobertura-maven-plugin/plugin-info.html
在pom.xml里配置:
<properties>
<cobertura.version>2.7</cobertura.version>
</properties> <build>
<pluginManagement> <plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<version>${cobertura.version}</version>
<configuration>
<check />
<formats>
<format>html</format>
<format>xml</format>
</formats>
<aggregate>true</aggregate>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>cobertura</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
2.2 Maven之Cobertura Maven Plugin:
2.3.分析结果指标及含义
1> Line Coverage(行覆盖率)
The percent of lines executed by this test run.
2> Branch Coverage(分支覆盖率)
The percent of branches executed by this test run.
3> Complexity(复杂度)
Average McCabe’s cyclomatic code complexity for all methods. This is basically a count of the number of different code paths in a method (incremented by 1 for each if statement, while loop, etc.)
4> N/A
Line coverage and branch coverage will appear as “Not Applicable” when Cobertura can not find line number information in the .class file. This happens for stub and skeleton classes, interfaces, or when the class was not compiled with “debug=true.”
2.4.注意细节:
1> 以上分析基于单模块(测试代码和源代码在同一个模块中),如果项目是多模块(测试代码和源代码在不同的模块中)则需要maven+Ant来具体分析,详细配置可以参考链接文档:
2> 如果要使用Ant,需要注意build.xml的配置
3> 如果要得到单个测试用例的测试覆盖率,可以使用idea自带的run with coverage
2.5 硬伤:Cobertura 不支持java1.8
当升级到java8之后,如果项目的代码中使用了java的lamda表达式(类似 list.stream.foreach( i -> System.out.println(i))等,当执行 mvn clean cobertura:cobertura 命令的时候,会出现如下如下信息:
[WARN] JavaNCSS got an error while parsing the java file
Encountered " ">" "> "" at line
Was expecting one of:
"assert" ...
"boolean" ...
"byte" ...
这个问题导致到原因是什么呢?
(The root cause of this issue is that the NCSS library ( https://github.com/codehaus/javancss ) that Cobertura uses is not able to handle Java8 syntax)。
javancss can't handle certain Java8 Code and you get an error while parsing.
The last commit was 2 years ago so you have to try an alternative like SonarCube
答案来自
https://github.com/cobertura/cobertura/issues/176
暂时没有什么好的解决方法,只能屏蔽一些错误信息。
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<version>2.7</version>
<configuration>
**<quiet>true</quiet>**
<formats>
<format>xml</format>
</formats>
<instrumentation>
**<ignoreTrivial>true</ignoreTrivial>**
</instrumentation>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>cobertura</goal>
</goals>
</execution>
</executions>
</plugin>
3 参考
https://tech.meituan.com/2017/06/16/android-jacoco-practace.html
https://www.javatt.com/p/89111