基本概念
有时候,对于一些重要的项目或者重点类,我们希望重点测试,但是我们怎么评估测试质量呢?
这个时候,或许就需要jacoco了。
接下来,我们就来了解一下jacoco的基本概念与使用。
以及在某些条件不允许的情况下,我们如何跳过某些类,再结合mvn参数,让我们的单元测试报告看起来不那么乱。
jacoco会分析:指令(C0)、分支(C1)、行、方法、类型和循环复杂度的覆盖率
- 指令(Instructions,C0覆盖率,Java字节代码指令)
- 分支(Branches,C1覆盖率,分支覆盖率)
- 循环复杂度(Cyclomatic Complexity,cxty)
- 行(line)
- 方法(method)
- 类(class)
jacoco结果显示:
分支:
- 无覆盖范围:该行没有分支执行(红色菱形)
- 部分覆盖:仅执行了该行中的一部分分支(黄色菱形)
- 全面覆盖:该行中的所有分支均已执行(绿色菱形)
行:
- 无覆盖:该行中没有指令被执行(红色背景)
- 部分覆盖:仅执行了该行中的一部分指令(黄色背景)
- 全面覆盖:该行中的所有指令均已执行(绿色背景)
如果上面内容比较抽象,可以看看下面具体实例。
基本使用
maven配置
对于maven项目可以参考下面的配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>vip.meet</groupId><artifactId>jacoco-test</artifactId><version>1.0.0</version><name>jacoco-test</name><description>单元测试报告</description><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.8.2</version><scope>test</scope></dependency><dependency><groupId>org.junit.platform</groupId><artifactId>junit-platform-launcher</artifactId><version>1.7.2</version><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>${java.version}</source><target>${java.version}</target><encoding>UTF8</encoding></configuration><version>3.8.1</version></plugin><plugin><groupId>org.jacoco</groupId><artifactId>jacoco-maven-plugin</artifactId><version>0.8.11</version><executions><execution><id>prepare-agent</id><goals><goal>prepare-agent</goal></goals><configuration><includes><!-- **/SayHi** --></includes><excludes></excludes></configuration></execution><execution><id>generate-code-coverage-report</id><phase>test</phase><goals><goal>report</goal></goals><configuration><formats>HTML</formats><includes>
<!-- **/SayHi.class--></includes><excludes>
<!-- **/SayHello.class--></excludes></configuration></execution></executions></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>2.22.2</version><configuration><skipTests>false</skipTests><testFailureIgnore>true</testFailureIgnore><forkMode>once</forkMode></configuration></plugin></plugins></build>
</project>
类与测试类
业务类:
public class SayHi {public static String hi(String name) {if (name == null) {return "名字不能为空";} else if (name.trim().isEmpty()) {return "名字不能为空字符串";} else {return "Hi " + name;}}
}
public class SayHello {public static String hello(String name) {if (name == null) {return "名字不能为空";} else if (name.trim().isEmpty()) {return "名字不能为空字符串";} else {return "Hello " + name;}}
}
测试类:
import org.junit.jupiter.api.Test;import static org.junit.jupiter.api.Assertions.*;class SayHiTest {@Testvoid getMessage() {assertEquals("Hi allen", SayHi.hi("allen"));SayHi.hi("");SayHi.hi(null);}
}
import org.junit.jupiter.api.Test;class SayHelloTest {@Testvoid hello() {SayHello.hello("allen");}
}
执行mvn命令,执行单元测试:
mvn clean test
输出的目录默认在项目目录下的target/site/jacoco目录下。
结果如下:
其中1是指令覆盖率,2是分支覆盖率,后面是没有被覆盖到的复杂度(行、方法、类)与总复杂度(行、方法、类)
Total部分是汇总信息。
我们可以接着往下点,可以看到具体的类、方法、行的覆盖信息。
【具体方法行覆盖信息】
jacoco常用配置
prepare-agent
jacoco.exec在这个阶段生成。
<plugin><groupId>org.jacoco</groupId><artifactId>jacoco-maven-plugin</artifactId><version>0.8.11</version><executions><execution><id>prepare-agent</id><goals><goal>prepare-agent</goal></goals><configuration><classDumpDir>target/site/jacoco/med-class</classDumpDir><excludes><exclude>vip.meet.SayHello</exclude></excludes><includes><include>vip.meet.SayHi</include></includes></configuration></execution></executions>
</plugin>
report阶段
最常用的就是生成报告的时候,我们可能不想报告太乱,我们可以跳过某些类的,或者只为某些类生成报告。
<execution><id>generate-code-coverage-report</id><phase>test</phase><goals><goal>report</goal></goals><configuration><!--定义输出的文件夹--><outputDirectory>target/jacoco</outputDirectory><!--执行数据的文件--><dataFile>${project.build.directory}/jacoco.exec</dataFile><!--要从报告中排除的类文件列表,支持通配符(*和?)--><excludes>**/api/**/SayHello*.class</excludes><!--包含生成报告的文件列表,支持通配符(*和?)--><includes>**/SayHi.class</includes><!--HTML 报告页面中使用的页脚文本--><footer></footer><!--生成报告的文件类型,HTML(默认)、XML、CSV--><formats>HTML</formats><!--生成报告的编码格式,默认UTF-8--><outputEncoding>UTF-8</outputEncoding><!--跳过执行的标签--><skip></skip><!--源文件编码--><sourceEncoding>UTF-8</sourceEncoding><!--HTML报告的标题--><title>${project.name}-单元测试报告</title></configuration>
</execution>
例如,我只想看SayHi的覆盖测试情况,就可以在report阶段配置includes为 **/SayHi.class
rule-规则检查
<plugin><groupId>org.jacoco</groupId><artifactId>jacoco-maven-plugin</artifactId><version>0.8.11</version><configuration><rules><rule implementation="org.jacoco.maven.RuleConfiguration"><element>BUNDLE</element><limits> <!-- 方法覆盖率最小值为80% --><limit implementation="org.jacoco.report.check.Limit"><counter>METHOD</counter><value>COVEREDRATIO</value><minimum>0.8</minimum></limit><!-- 分支覆盖最小值为50% --><limit implementation="org.jacoco.report.check.Limit"><counter>BRANCH</counter><value>COVEREDRATIO</value><minimum>0.5</minimum></limit><!-- 类必须全部被覆盖 --><limit implementation="org.jacoco.report.check.Limit"><counter>CLASS</counter><value>MISSEDCOUNT</value><maximum>0</maximum></limit></limits></rule></rules>
</configuration>
</plugin>
rule参数:
- element:范围,bundle、package、class、sourcefile、method
- includes:需要检查的元素集合名
- excludes:不需要被检查的元素
- imits:用于检查的limits
limit参数:
- counter:INSTRUCTION(指令)、LINE(行)、BRANCH(分支)、COMPLEXITY(复杂度)、METHOD(方法)、CLASS(类)
- value:TOTALCOUNT(总数量)、MISSEDCOUNT(未覆盖数量)、COVEREDCOUNT(覆盖数量)、MISSEDRATIO(未覆盖率)、COVEREDRATIO(覆盖率)
- minimum:最小值
- maximum:最大值
rule检查不满足条件的时候,mvn test阶段直接报错。
rule是在check阶段执行的
<execution><id>check</id><goals><goal>check</goal></goals>
</execution>
聚合项目配置
聚合项目可以单独添加一个子项目,来做聚合操作,只需配置:report-aggregate
<execution><id>jacoco-report-aggregate</id><phase>test</phase><goals><goal>report-aggregate</goal></goals>
</execution>
具体pom配置可以参考下面
父项目pom配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>vip.meet</groupId><artifactId>jacoco-aggregate-test</artifactId><version>1.0.0</version><packaging>pom</packaging><name>jacoco-test</name><description>单元测试覆盖率集合报告</description><modules><module>jacoco-one</module><module>jacoco-two</module><module>jacoco-aggregate</module></modules><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.8.2</version><scope>test</scope></dependency><dependency><groupId>org.junit.platform</groupId><artifactId>junit-platform-launcher</artifactId><version>1.7.2</version><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>${java.version}</source><target>${java.version}</target><encoding>UTF8</encoding></configuration><version>3.8.1</version></plugin><plugin><groupId>org.jacoco</groupId><artifactId>jacoco-maven-plugin</artifactId><version>0.8.11</version><executions><execution><id>prepare-agent</id><goals><goal>prepare-agent</goal></goals><configuration></configuration></execution><execution><id>generate-code-coverage-report</id><phase>test</phase><goals><goal>report</goal></goals><configuration><formats>HTML</formats></configuration></execution></executions></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>2.22.2</version><configuration><skipTests>false</skipTests><testFailureIgnore>true</testFailureIgnore><forkMode>once</forkMode></configuration></plugin></plugins></build>
</project>
配置聚合项目jacoco-maven-plugin其实最好放到子项目,按需配置,这里为了直观直接配置到了父项目的pom中,就意味着所有的子项目都继承了jacoco-maven-plugin。
我们可以看到配置了3个子项目:
<modules><module>jacoco-one</module><module>jacoco-two</module><module>jacoco-aggregate</module>
</modules>
jacoco-one和jacoco-two是正常的子项目,jacoco-aggregate是用来做聚合报告的。
主要需要看一下jacoco-aggregate的pom配置:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>vip.meet</groupId><artifactId>jacoco-aggregate-test</artifactId><version>1.0.0</version></parent><artifactId>jacoco-aggregate</artifactId><packaging>jar</packaging><dependencies><dependency><groupId>vip.meet</groupId><artifactId>jacoco-one</artifactId><version>1.0.0</version></dependency><dependency><groupId>vip.meet</groupId><artifactId>jacoco-two</artifactId><version>1.0.0</version></dependency></dependencies><build><plugins><plugin><groupId>org.jacoco</groupId><artifactId>jacoco-maven-plugin</artifactId><version>0.8.11</version><executions><execution><id>default-prepare-agent</id><goals><goal>prepare-agent</goal></goals><configuration></configuration></execution><execution><id>default-report</id><phase>test</phase><goals><goal>report</goal></goals></execution><execution><id>jacoco-report-aggregate</id><phase>test</phase><goals><goal>report-aggregate</goal></goals></execution></executions></plugin></plugins></build>
</project>
2点非常重要:
- 首先要通过dependency把需要统计覆盖率的项目依赖引入
- 单独配置jacoco-maven-plugin,因为需要report-aggregate
示例项目下载