Jacoco代码覆盖率报告分析

Jacoco代码覆盖率统计维度、报告字段说明、报告详细分析描述。并为精准测试、健壮性测试提供指导。

Jacoco代码覆盖率统计维度

Jacoco是从代码指令(Instructions, C0coverage),分支(Branches, C1coverage),圈复杂度(Cyclomatic Complexity),行(Lines),方法(Methods),类(Classes)等维度进行分析的。

Jacoco生成报告字段说明

image-20201019104804738

字段名称 说明 描述
Element 元素 最外层展示分组名称,依次为包>类>方法
Missed Instructions Cov. 指令覆盖,字节码中指令 1. Jacoco最小计数单元是单个Java二进制指令代码。
2. 指令覆盖率提供了代码是否被执行的信息。
Missed Branches Cov. 分支覆盖率 1. Jacoco计算分支覆盖率,包括所有的if和switch语句。

2. 用钻石表示,分支覆盖不能看行。
3. 这是度量一个方法里面的总分支数,确定执行和不执行的分支数量。
4. 分支覆盖率总是可用的,即使class文件里面没有调试信息。
注意:异常处理时不在分支度量里面统计的。
Missed Cxty 圈复杂度 1. Jacoco为每一个非抽象方法计算复杂度,最终计算出类、包和组的复杂度。由McCabe1996可知圈复杂度的定义是,在(线性)组合中,计算在一个方法里面所有可能路径的最小数目。
2. 复杂度可以作为度量单元测试是否完整覆盖所有场景的一个依据,复杂度即使在没有调试信息的情况下也可以计算。
3. 圈复杂度V(G)的正式定义是基于方法的控制流图的有向图表示的:V(G)=E-N+2 E是边界数量,N是节点的数量。
4. Jacoco的复杂度计算方程:V(G)=B-D+1 B是分支的数量,D是决策点的数量。
5. 基于每个分支的覆盖情况,Jacoco也为每个方法计算覆盖和缺失的复杂度,缺失的复杂度同样表示测试案例没有完全覆盖到这个模块。
注意:Jacoco不将异常处理作为分支,try/catch块不增加复杂度。
Missed Lines 代码行 1. class文件使用debug信息编译后,就可以统计行的覆盖率信息。一行源码是否被执行,要看这一行中是否至少有一个指令被执行。
2. 一行代码一般被编译成多个二进制代码指令。
Missed Methods 方法 每一个非抽象方法至少包含一个指令,一个方法是否执行取决于方法中是否有至少一个指令被执行。
在Jacoco中,构造器和静态初始化同样会像方法一样统计,其中一些方法可能没有可以直接对应的源码,比如默认构造器或常量的初始化命令。
Missed Classes 一个方法是否执行取决于类中是否至少有一个方法被执行。
注意:Jacoco认为构造器和静态初始化都是方法,Java的接口一般包含静态初始化,所以接口也同样被认为是可执行的类。

注:Missed表示未覆盖

Jacoco生成报告详细分析

将生成的Jacoco报告打开,如何生成Jacoco的报告,后续会介绍,本篇主要介绍Jacoco报告的分析。为了更好的分析Jacoco覆盖率,新建了一个工程进行演练。

Github:https://github.com/jinglv/spring-boot-demo

展示当前所有的包,包的覆盖率取决于类的覆盖情况。

image-20201019112450851

点进去一个包中,显示该包下所有的类,类的覆盖率取决于方法的覆盖情况。

image-20201019112621716

方法

点进去一个类中,显示该类下所有的方法名,方法的覆盖率取决于方法内的覆盖情况。

image-20201019112750113

Jacoco覆盖率的详细分析

以上已经介绍了Jacoco代码覆盖统计的维度,接下来针对于实例,进行报告中字段的详细说明。

Missed Instructions

image-20201019113056713

说明:红色进度条表示未覆盖,绿色进度条表示已覆盖,Cov为总体覆盖率。

Total:1176表示没有覆盖的指令,1517表示总的指令

Cov:表示覆盖率

Jacoco计算的最小单位就是字节码指令。指令覆盖表明了在所有的指令中,哪些被执行过以及哪些没有被执行。这项指数完全独立与源码格式并且在任何情况下有效,不需要类文件的调试信息。

Missed Branches

image-20201019113555485

说明:红色进度条表示未覆盖,绿色进度条表示已覆盖,Cov为总体覆盖率。

Total:168表示没有覆盖的分支,172表示总的分支

Cov:表示覆盖率

从上面的图看出Missed Instructions(指令)覆盖率为22%,Missed Branches(分支)覆盖率为2%,为什么会是这样的情况呢?原因是:代码行覆盖的并不代表分支都已覆盖。

实例分析:

image-20201020131918623

从上图看出,ofException这个方法,指令覆盖了85%,分支覆盖了50%,这时进入到方法中查看:

image-20201020132050555

查看该方法调用的代码看,只返回了ApiCodeEnum.FAIL.getCode(),说明该条件只执行了结果为true的情况,没有执行结果为false的情况,因此指令覆盖和分支覆盖都不全。

Missed Cxty

image-20201019115800669

Total:166表示没有覆盖到的圈复杂度,213表示总共的圈复杂度

实例分析:进入到包service.impl包下,再进入CalcServiceImpl实例类中

image-20201020113119147

查看该实现类里,有加减乘除四个方法,圈复杂度的显示加减乘都是1,除是2,接下来再进入的方法内再次查看:

image-20201020113335084

从圈复杂度的介绍可知,圈复杂度是根据代码中判定条件的节点来计算的,jacoco在统计代码时也是根据代码中有多少个判定节点来算(代码里有多少if、for、while…的判定节点来统计的),从上面的代码来看,add、sub、mul这三个方法,没有判定节点,计算是0+1,因此这个三个方法都是复杂度都是1,div方法中有一个if节点,计算是1+1,这个方法的复杂度就是2,和jacoco的报告就对上了。

将上面的代码,进行修改并执行

image-20201022183727681

查看jacoco的报告

image-20201022183758173

由于在代码中添加了if的判定条件,统计的圈复杂度明显增加,通过计算公式的计算也是一致的。

根据官网介绍,jacoco的圈复杂度的计算公式是:V(G) = B - D + 1B是分支数量,D是决策点数量,查看一些资料后,并不理解这个分支数量和决策点数量具体指什么,因此还是根据圈复杂度里介绍的计算公式更容易理解一些。

Missed.Lines、Methods、classes

image-20201019120155106

Lines:代码行,共有177行,98行未覆盖到(例如:else是不统计到Lines的)

Methods:方法,共有127个方法,80个未覆盖到

Classes:类,共有18个类,4个未覆盖到

Missed表示未覆盖到

覆盖率标识

钻石代表分支覆盖情况

  • 红色钻石:这一行没有分支被执行
  • 黄色钻石:这一行中只有部分分支被执行
  • 绿色钻石:这一行的所有分支都被执行

背景颜色代表指令覆盖率

  • 红色背景:这一行并没有任何指令被执行
  • 黄色背景:这一行的部分指令被执行
  • 绿色背景:这一行的所有指令都被执行了