JaCoCo覆盖率计数器介绍

jacoco

阅读本文大约需要5分钟

注意:此文译自JaCoCo官方介绍文档,如有出错之处,烦请指出,不胜感激。点击此处,查看原文

覆盖率计数器

JaCoCo使用一组不同的计数器来计算覆盖率指标。这些计数器根据Java类文件包含的信息(基本上是Java字节码指令和调试信息有选择地嵌入类文件)被分成不同种类。因此,即使在没有源码的情况下,这种方法也允许高效的动态测量和分析应用程序。

在大多数情况下,收集到的信息可以被映射回源代码,然后可视化成行级别的覆盖率报告。然而,这种方法也有局限性。调试信息必须被编译到类文件,以便计算行粒度覆盖率,并提供源代码高亮显示。并不是所有的Java语言结构都可以直接编译成字节码,在这种情况下,Java编译器创建所谓的合成代码有时会导致意想不到的代码覆盖率结果。

指令(C0 Coverage)

最小单元JaCoCo计数是单个Java字节码指令,指令覆盖提供了被执行或未被执行的代码量信息。这个指标是完全独立于源格式的, 即使在没有调试信息的类文件,也是可用的,。

分支(C1 Coverage)

JaCoCo也计算所有if和switch语句的分支覆盖率。这个指标项计算在一个方法中的总分支数,并决定了已执行和未执行分支的数量。即使在没有调试信息的类文件,分支覆盖也总是可用的。注意,在此计数器的定义中,异常处理并不属于分支。

如果类文件被编译了调式信息决策点,就能够被相应地映射回源码行以及高亮显示:

  • 未覆盖:所有分支行都未被执行(红色钻石)
  • 部分覆盖:只有一部分行分支被执行(黄色钻石)
  • 全覆盖:所有的行分支都已被执行(绿色钻石)

圈复杂度

JaCoCo也为每个非抽象方法计算圈复杂度,总结类、包和组的复杂性。根据McCabe1996的定义,圈复杂度是,在(线性)组合,通过一个方法来生成所有可能的路径中的最小数量路径。因此,复杂性值可以作为单元测试用例的数量完全覆盖一个软件某一块的指标。即使在没有调试信息的类文件,复杂度也通常都是可计算的。

圈复杂度的正式定义v(G)是基于方法的控制流图表示的有向图:

v(G) = E - N + 2

E表示边缘数,N表示节点数。JaCoCo通过以基于分支数(B)和决策点(D)的等效方程来计算一个方法的圈复杂度:

v(G) = B - D + 1

根据每个分支的覆盖率情况,JaCoCo还为每个方法计算已覆盖和未覆盖的复杂性,未覆盖复杂度又为完全覆盖一个模块所缺失的测试用例数提供了指标。注意,JaCoCo并不考虑异常处理分支,try / catch块也不会增加复杂性。

对所有已编译调试信息的类文件,每一行的覆盖率信息也可以计算。当至少一条指令被分配到某一源码行时,该源码行被认为已经执行。

由于一行通常 编译多个字节码指令,每一行包含的源代码高亮显示有三种不同的状态:

  • 未覆盖:该行没有指令被执行(红色背景)
  • 部分覆盖:该行只有部分指令被执行(黄色背景)
  • 全覆盖:该行包含的所有指令都被执行(绿色背景)

根据源格式,一行源代码可能指定了多个方法或多个类。因此,方法的行计数,不能简单地添加到获得包含类的总数。这同样适用于在一个源文件的多个类。JaCoCo基于实际的源代码行覆盖来计算类和源文件的覆盖率。

方法

每个非抽象方法至少包含一条指令。当方法至少一条指令被执行,则被认为该方法被执行过。因为JaCoCo是基于字节码级别的,构造函数和静态初始化也被当作方法计算。 其中有些方法,如隐式,从而生成默认构造函数或常量初始值设定,可能没有直接对应到Java源代码中。

当一个类至少有一个方法被执行过,则认为该类被执行过。注意,JaCoCo 将构造方法和静态初始化方法也当作被执行过的方法。包含静态初始化方法的Java接口类型,也被当作已执行的类。