Java单元覆盖率工具JaCoCo详细理解和使用(配置+示例)

article/2025/9/28 18:09:53

一、代码覆盖率理解

        代码覆盖(Code coverage)是软件测试中的一种度量,描述程序中源代码被测试的比例和程度,所得比例称为代码覆盖率。

        简单来理解,就是单元测试中代码执行量与代码总量之间的比率。

      Java常用的单元测试覆盖率框架有:JaCoCo、EMMA和Cobertura,本篇文章主要介绍JaCoCo的使用。

二、JaCoCo理解

        JaCoCo官方文档:https://www.eclemma.org/jacoco/trunk/doc/index.html

        JaCoCo应该为基于Java VM的环境中的代码覆盖率分析提供标准技术。重点是提供一个轻量级,灵活且文档齐全的库,以与各种构建和开发工具集成。

产品定义

1.功能特征:

  • 指令(C0),分支(C1),行,方法,类型和循环复杂度的覆盖率分析
  • 基于Java字节码,因此无需源文件也可以工作
  • 通过基于 Java-agent 的即时检测进行简单集成。其他集成方案(例如自定义类加载器)也可以通过API来实现
  • 与框架无关的:与基于Java VM的应用程序(如纯Java程序,OSGi框架,Web容器或EJB服务器)平滑集成
  • 与所有已发布的Java类文件版本兼容
  • 支持不同的JVM语言
  • 几种报告格式(HTML,XML,CSV)
  • 远程协议和JMX控制可在任何时间点从coverage agent请求执行数据dump
  • Ant任务,用于收集和管理执行数据并创建结构化的覆盖率报告
  • Maven插件可收集覆盖率信息并在Maven构建中创建报告

非功能特征:

  • 简单的用法以及与现有构建脚本和工具的集成
  • 良好的性能和最小的运行时开销,尤其是对于大型项目
  • 轻量级实现,对外部库和系统资源的依赖性最小
  • 全面的文档
  • 完整记录的API(JavaDoc)以及与其他工具集成的示例
  • 基于JUnit测试用例的功能全面的回归测试

2. JaCoCo集成

        JaCoCo集成主要包括JaCoCo/EclEmma 项目提供的集成和第三方集成

    1)JaCoCo/EclEmma 项目集成  

        

     2) 第三方集成

3. 覆盖率计数器 - Coverage Counters

JaCoCo使用一组不同的计数器来计算覆盖率指标。所有这些计数器都从Java类文件中包含的信息派生而来,这些信息基本上是Java字节码指令以及调试信息(可选地嵌入在类文件中)。即使没有可用的源代码,这种方法也可以对应用程序进行高效的即时检测和分析(instrumentation and analysis)。在大多数情况下,可以将收集到的信息映射回源代码,并可视化到行级粒度。无论如何,这种方法存在局限性。必须使用调试信息编译类文件,以计算行级覆盖率并提供源高亮显示。并非所有Java语言构造都可以直接编译为相应的字节码。在这种情况下,Java编译器会创建所谓的合成代码,有时会导致意外的代码覆盖率结果。

以下是JaCoCo统计的指标维度

    1)指令 - Instructions(C0覆盖率)

JaCoCo计数的最小单位是单个Java字节代码指令。指令覆盖率提供有关已执行或遗漏(executed or missed)的代码量的信息。该度量完全独立于源格式,并且即使在类文件中没有调试信息的情况下也始终可用。

    2)分支 - Branches(C1覆盖率)

JaCoCo还为所有if和switch语句计算分支覆盖率。此度量标准统计方法中此类分支的总数,并确定已执行或遗漏的分支的数量。分支覆盖始终可用,即使类文件中没有调试信息也是如此。请注意,在此计数器定义的上下文中,异常处理不视为分支。

如果尚未使用调试信息编译类文件,则可以将决策点映射到源代码行并高亮:

  • 无覆盖范围:该行没有分支执行(红色菱形)
  • 部分覆盖:仅执行了该行中的一部分分支(黄色菱形)
  • 全面覆盖:该行中的所有分支均已执行(绿色菱形)

    3)循环复杂度 - Cyclomatic Complexity

JaCoCo 还为每种非抽象方法计算循环复杂度,并汇总了类,包和组的复杂度。根据 McCabe1996 的定义,循环复杂度是可以(线性)组合生成一种方法的所有可能路径的最小路径数。因此,复杂度值可以作为完全覆盖某个软件的单元测试用例数量的指示。即使类文件中没有调试信息,也总是可以计算复杂度数字。

循环复杂度v(G)的形式定义基于方法的控制流图作为有向图的表示:

v(G)= E- N 2

其中,E是边数,N是节点数。 JaCoCo根据分支数(B)和决策点数(D)使用以下等效方程式计算方法的循环复杂度:

v(G)= B - D + 1

根据每个分支的覆盖状态,JaCoCo还可以计算每种方法的覆盖和遗漏复杂度。缺少的复杂性再次表明完全覆盖模块的测试用例的数量。请注意,由于JaCoCo不考虑异常处理,因为分支try / catch块也不会增加复杂性。

   4)行

对于已使用调试信息编译的所有类文件,可以计算各个行的覆盖率信息。当已执行至少一个分配给该源代码行的指令时,该源代码行被视为已执行。

由于单行通常会编译为多字节代码指令,因此,源代码高亮显示每行包含源代码的三种不同状态:

无覆盖:该行中没有指令被执行(红色背景)

部分覆盖:仅执行了该行中的一部分指令(黄色背景)

全面覆盖:该行中的所有指令均已执行(绿色背景)

根据源格式,源代码的一行可能会引用多个方法或多个类。因此,不能简单地添加方法的行数以获得包含类的总数。单个源文件中的多个类的行也是如此。 JaCoCo根据覆盖的实际源代码行计算类和源文件的代码行覆盖率。

  5)方法

 每个非抽象方法都包含至少一条指令。当至少一个指令已被执行时,一种方法被视为已执行。由于JaCoCo在字节码级别上工作,因此构造函数和静态初始化程序也被视为方法。这些方法中的某些方法在Java源代码中可能没有直接的对应关系,例如隐式生成的常量的默认构造函数或初始化器。

  6)类

当至少一个类的方法已执行时,该类被视为已执行。 请注意,JaCoCo将构造函数以及静态初始化程序视为方法。 由于Java接口类型可能包含静态初始化器,因此此类接口也被视为可执行类。

三、Maven设置JaCoCo插件

  1. 引入Maven插件        
<!-- https://mvnrepository.com/artifact/org.jacoco/jacoco-maven-plugin -->
<dependency><groupId>org.jacoco</groupId><artifactId>jacoco-maven-plugin</artifactId><version>0.8.7</version>
</dependency>

        2. 配置该插件的执行标签<executions>

        对于运行简单的单元测试,在执行标签中设置的两个目标可以正常工作。最低限度是设置准备代理(prepare-agent)和报告目标(report),配置如下:

<plugin><groupId>org.jacoco</groupId><artifactId>jacoco-maven-plugin</artifactId><version>0.8.6</version><executions><execution><id>prepare-agent</id><goals><goal>prepare-agent</goal></goals></execution><execution><id>report</id><phase>test</phase><goals><goal>report</goal></goals></execution></executions></plugin>

       

prepare-agent goal: prepare-agent 目标准备 JaCoCo 运行时代理以记录执行数据。它记录了执行的行数、回溯的行数等。默认情况下,将执行数据写入文件target/jacoco-ut.exec。

report goal: 报告目标根据 JaCoCo 运行时代理记录的执行数据创建代码覆盖率报告。由于我们已经指定了阶段属性,报告将在测试阶段编译后创建。默认从文件中读取执行数据target/jacoco-ut.exec,将代码覆盖率报告写入目录target/site/jacoco/index.html。

所有配置的Goals,详见官网https://www.eclemma.org/jacoco/trunk/doc/maven.html

其中比较常用的是 prepare-agent、report和check

report goal的详细配置如下:

<execution><id>report</id><phase>test</phase><goals><goal>report</goal></goals><configuration><!--定义输出的文件夹--><outputDirectory>target/jacoco-report</outputDirectory><!--执行数据的文件--><dataFile>${project.build.directory}/jacoco.exec</dataFile><!--要从报告中排除的类文件列表,支持通配符(*和?)。如果未指定则不会排除任何内容--><excludes>**/test/*.class</excludes><!--包含生成报告的文件列表,支持通配符(*和?)。如果未指定则包含所有内容--><includes></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>

check goal可以配置检查相关的设置,比如配置最低覆盖率为0.9(90%)

<execution><id>jacoco-check</id><goals><goal>check</goal></goals><configuration><rules><rule><element>PACKAGE</element><limits><limit><counter>LINE</counter><value>COVEREDRATIO</value><minimum>0.9</minimum></limit></limits></rule></rules></configuration>
</execution>

如果覆盖率未达到最低限制,则mvn test会报错

[INFO] Analyzed bundle 'test' with 1 classes
[WARNING] Rule violated for package com.**.examples.jacoco: lines covered ratio is 0.8, but expected minimum is 0.9

     四、示例

        创建maven项目,引入

        Junit5-单元测试

        jacoco-覆盖率统计

        maven依赖和插件配置如下:

<dependencies><!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api --><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.8.2</version><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>11</source><target>11</target><encoding>UTF8</encoding></configuration><version>3.8.1</version></plugin><plugin><artifactId>maven-surefire-plugin</artifactId><version>2.22.2</version></plugin><plugin><artifactId>maven-failsafe-plugin</artifactId><version>2.22.2</version></plugin><plugin><groupId>org.jacoco</groupId><artifactId>jacoco-maven-plugin</artifactId><version>0.8.7</version><executions><execution><goals><goal>prepare-agent</goal></goals></execution><execution><id>report</id><phase>test</phase><goals><goal>report</goal></goals><configuration><!--定义输出的文件夹--><outputDirectory>target/jacoco-report</outputDirectory></configuration></execution></executions></plugin></plugins>
</build>

  创建MessageBuilder类

public class MessageBuilder {public String getMessage(String name) {StringBuilder result = new StringBuilder();if (name == null || name.trim().length() == 0) {result.append("empty!");} else {result.append("Hello " + name);}return result.toString();}
}

编写单元测试类MessageBuilderTest

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;public class MessageBuilderTest {@Testpublic void testGetMessage1() {MessageBuilder obj = new MessageBuilder();assertEquals("Hello test", obj.getMessage("test"));}
}

mvn clean test 进行测试并生成覆盖率的统计

在生成目录中找到index.html文件

点击Element可以查看详细的情况

 我们发现分支中只执行了else的部分

测试类中增加为空的情况

@Test
public void testGetMessage2() {MessageBuilder obj = new MessageBuilder();assertEquals("empty!", obj.getMessage(""));
}

再次 mvn clean test

生成的统计信息如下:

分支覆盖率只有75%,原因if语句中的为null情况未覆盖

继续添加测试用例(为null的情况)

@Test
public void testGetMessage3(){MessageBuilder obj = new MessageBuilder();assertEquals("empty!", obj.getMessage(null));
}

 分支覆盖率终于达到了100%


http://chatgpt.dhexx.cn/article/ULltUPif.shtml

相关文章

怎么自学C语言 入门

第一阶段&#xff1a;C语言基础 在开始学习C语言基础时&#xff0c;要反问自己为什么学C语言&#xff0c;如何学好C语言。同时要知道什么是C语言以及C语言的发展。 当对这些知识有了一个概念之后&#xff0c;就正式开始学习C语言了&#xff0c;其中&#xff0c;可能会学到数据…

C语言入门03

运算符 流程控制语句

【C语言】C语言入门经典题目(范围广,内容多)

✨作者&#xff1a;小孙的代码分享 ✨专栏&#xff1a;《C语言入门》 ✨送给各位的一句话&#xff1a;空杯心态 才能学到新知 ✨希望大家看完这些题目有所收获&#xff0c;别忘了&#xff0c;点赞评论&#xff01; 目录 前言 &#x1f604; 字符转ASCII码&#x1f525; 判断闰…

C语言入门1:Hello World

C语言入门1&#xff1a;Hello World 1、第一个C语言程序&#xff1a;Hello World&#xff01;&#xff08;1&#xff09;编辑hello.c文件&#xff08;2&#xff09;在hello.c下面编辑如下代码&#xff0c;并保存&#xff08;3&#xff09;编译hello.c文件&#xff08;4&#xf…

C语言入门(1)——Hello World

C语言入门(1)——Hello World 1. 第一个C语言程序&#xff1a;Hello World 开始的第一个程序是一个最简单的程序&#xff0c;也就是最经典的Hello World程序&#xff0c;它的功能为打印出Hello World。程序的内容非常简单&#xff0c;也是C语言编写程序的基本结构框架。 1.示…

c语言入门介绍 Hello, World

相信每一个c语言的初学者的第一个程序都是从 Hello, World! 开始的吧。不过新手刚开始接触&#xff0c;应该有许多不懂的地方&#xff0c;下面我就通过Visual Studio 2019编程环境来简单介绍一下这个程序。 第一句是由于Visual Studio 2019编程环境中scanf函数&#xff08;输入…

C语言入门的三个简易程序

文章目录 一、打印100&#xff5e;200之间的素数方法一&#xff1a;1.思路2.代码如下3.结果图 方法二:1.思路2.代码如下3.结果图 二、打印乘法口诀表1.思路2.代码如下3.结果图 三、输出1000&#xff5e;2000之间的闰年1.思路2.代码如下3.结果图 四、提升 一、打印100&#xff5…

C语言入门 九九乘法表

利用C语言 输出九九乘法表 用双重循环来完成&#xff0c;外循环用变量i控制输出的行数&#xff08;总共九行&#xff09;同时i也是被乘数。 内循环用变量j循还控制列号&#xff0c;同时j也是乘数&#xff0c;规定列号不能大于行号&#xff0c;每一行输出结束后换行。 #inclu…

C语言入门之入门

错误示范 #include<stdio.h> //头文件int mian(void){printf("你好世界&#xff01;"); //标准输出函数&#xff0c;是一个库函数return 0;}上面这个程序是有问题的&#xff0c;第一次写的时候没有运行成功 编译环境是 Visual C2010 查找之后发现是main 写…

C语言入门(初识C语言)

C语言入门&#xff08;初识C语言&#xff09; 前言什么是C语言&#xff1f;为什么选择C语言&#xff1f;计算机语言的发展高级计算机语言中的经典&#xff1a;C语言 初识C语言&#xff08;正片开始&#xff09;一.第一个C语言程序&#xff08;你好&#xff0c;C语言&#xff09…

网络流(一)最大流问题EdmondsKarp和最小费用最大流

一、最大流问题 如下图所示&#xff0c;假设需要把一些物品从结点S&#xff08;称为源点&#xff09;运送到结点t&#xff08;称为汇点&#xff09;&#xff0c;可以从其它结点中转。每条边上的权值&#xff08;左图&#xff09;表示该条路径最多能运送的物品数&#xff0c;右…

最大流,最小费用最大流:解析 + 各种板子

网络流初步 Edmond-Karp算法 网络流的基本概念 源点&#xff0c;这个点只有流量的流出&#xff0c;没有流入。汇点&#xff0c;这个点只有流量的流入&#xff0c;没有流出。容量&#xff0c;每条有向边的最大可承受的流的理论大小。流量&#xff0c;每条有向边的最大可承受的…

分配问题(最小费用最大流)

题目&#xff1a;有 n 件工作要分配给 n 个人做。第 i个人做第 j件工作产生的效益为 。试设计一个将 n件工作分配给 n个人做的分配方案&#xff0c;使产生的总效益最大。 分析&#xff1a;这是一道多解法问题&#xff0c;可以用带剪枝的搜索和图论的最小费用最大流的方法来做&…

最小费用最大流算法 网络流

最小费用最大流算法 图片来源 《趣学算法》 人民邮电出版社 陈小玉 代码实现 /* 参考:《趣学算法》陈小玉 人民邮电出版社 最小费用最大流---最小费用路算法 问题分析:在实际应用中&#xff0c;要同时考虑流量和费用&#xff0c;每条边除了给定容量之外&#xff0c;还定义了一…

网络流----最小费用最大流(EK+SPFA)

先来介绍一下什么是费用流&#xff08;部分内容参考bilibili董晓算法&#xff09; 给定一个网络G&#xff08;V&#xff0c;E&#xff09;&#xff0c;每条边有容量限制w(u,v)&#xff0c;还有单位流量的费用c(u,v)。 当&#xff08;u,v&#xff09;的流量为f(u,v)时&#xf…

【图论】网络流——最大流和最小费用流

【图论】网络流——最大流和最小费用流 文章目录 【图论】网络流——最大流和最小费用流1. 最大流问题1.1 基本概念1.2 寻求最大流的算法&#xff08;Ford-Fulerson&#xff09;1.3 matlab求最大流 2. 最小流问题2.1 基本概念2.2 求最小流的迭代算法2.3 matlab 求最大费用最小流…

网络流(2)-----最小费用最大流(附带讲解SPFA算法)

一.最小费用最大流&#xff08;简称费用流&#xff09;概念 1.什么是费用流问题 上篇文章我们讲解了最大流问题&#xff0c;那什么是最小费用最大流呢&#xff1f;听名字就可以看出&#xff0c;我们要在满足最大流的同时找到达成最大流的最小费用。对于一个网络流&#xff0c…

最小费用最大流问题详解

最小费用最大流问题 一、问题描述 在网络中求一个最大流f&#xff0c;使流的总输送费用最小。 b ( f ) ∑ ( v i , v j ) b i j f i j b(f) \sum\limits_{(v_i,v_j)} b_{ij} f_{ij} b(f)(vi​,vj​)∑​bij​fij​ ) &#xff08; b i j b_{ij} bij​ 表示弧 ( v i , v j…

最小费用最大流问题与算法实现(Bellman-Ford、SPFA、Dijkstra)

摘要 今日&#xff0c;对最小费用最大流问题进行了一个简单的研究&#xff0c;并针对网上的一些已有算法进行了查找和研究。博客和资料很多&#xff0c;但是留下的算法很多运行失败、出错&#xff0c;或者意义不明。这里&#xff0c;个人对其中的Bellman-Ford、SPFA、改进的Di…

如何区分冲突域和广播域?

举个例子&#xff0c;原始社会的人都生活在山洞里&#xff0c;每个人都住在自己的山洞里&#xff0c;洞穴的中间只有一个狭长的走廊可以出去&#xff0c;平时出去玩&#xff0c;大哥都会喊一嗓子&#xff0c;有人出去打猎不&#xff0c;所有人都听得到&#xff0c;这就是一个广…