基本概念
有时候,对于一些重要的项目或者重点类,我们希望重点测试,但是我们怎么评估测试质量呢?
这个时候,或许就需要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 {
@Test
void getMessage() {
assertEquals("Hi allen", SayHi.hi("allen"));
SayHi.hi("");
SayHi.hi(null);
}
}
import org.junit.jupiter.api.Test;
class SayHelloTest {
@Test
void 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