JaCoCo 类标识符(Class IDs)详细分析与总结
类标识符(Class IDs)概述
类标识符是64位整数值,例如十六进制表示的 0x638e104737889183
。JaCoCo 的类标识符的生成机制是内部实现细节,目前是通过原始类文件的 CRC64 校验和来创建。
类标识符的用途
- 类标识符用于唯一标识 Java 类。
- 在运行时,每个加载的类的执行数据会被采样,通常存储到
*.exec
文件中。 - 在分析时(例如生成报告时),类标识符用于将分析的类与执行数据关联起来。
类标识符的优势
- 允许区分不同版本的类,例如当多个版本的应用程序部署到应用服务器或包含不同版本的库时。
- 类标识符是 JaCoCo 保持最小运行开销和小
*.exec
文件的前提,即使对于非常大的被测试应用程序也是如此。
类标识符的缺点
- 类标识符标识一个类的具体版本,这在运行时和分析时使用不同类的情况下会导致问题。
运行时和分析时使用不同类的问题
- 在这种情况下,执行数据无法与分析的类关联,导致这些类报告的覆盖率为0%。
如何检测类标识符问题
- 类标识符不匹配的典型症状是,尽管在测试期间执行了类,但它们没有显示为已覆盖。这可以通过 HTML 报告轻松检测到:打开右上角链接的 Sessions 页面,你会看到一个列表,列出了所有已收集执行数据的类。找到有问题的类,并检查条目是否有指向相应覆盖率报告页面的链接。如果没有链接,则表示运行时使用的类和用于创建报告的类之间存在类标识符不匹配。
导致不同类标识符的原因
- 类标识符仅对完全相同的类文件(逐字节)是相同的。
- 有几种原因可能导致你得到不同的类文件,例如使用不同的工具链编译 Java 源文件:
- 不同的编译器供应商(例如 Eclipse 与 Oracle JDK)
- 不同的编译器版本
- 不同的编译器设置(例如调试与非调试)
- 后处理类文件(例如混淆、AspectJ 等)通常也会改变类文件。只要你在运行时和分析时使用相同的类文件,JaCoCo 就会正常工作。因此,创建这些类文件的工具链并不重要。
处理运行时修改类的变通方法
- 如果你的设置中在运行时修改了类,有一些变通方法可以使 JaCoCo 正常工作:
- 如果你使用另一个 Java 代理,请确保在命令行中首先指定 JaCoCo 代理。这样,JaCoCo 代理应该能看到原始类文件。
- 指定 JaCoCo 代理的
classdumpdir
选项,并在报告生成时使用转储的类。注意,只有加载的类会被转储,即根本没有执行的类将不会在报告中显示为未覆盖。 - 在运行测试之前使用离线插桩。这样,在任何运行时修改发生之前,类就会被 JaCoCo 插桩。注意,在这种情况下,报告必须使用原始类生成,而不是使用插桩的类。
为什么 JaCoCo 不能简单地使用类名来标识类
- 要理解为什么 JaCoCo 不能依赖类名,我们需要看看 JaCoCo 如何测量代码覆盖率。
- JaCoCo 使用所谓的 探针(probes) 来跟踪执行。探针是插入原始类文件的额外字节码指令,它们会在执行时记录并报告给 JaCoCo 运行时。这个过程称为 插桩(instrumentation)。为了保持最小的运行开销,只在“战略性”位置插入少量探针。这些探针位置是通过分析一个类的所有方法的控制流来确定的。结果,每个插桩的类都会产生一个表示探针是否被执行的布尔标志列表
n
。JaCoCo 的*.exec
文件简单地为每个类标识符存储一个布尔数组。 - 在分析时,例如生成报告时,使用
*.exec
文件获取探针执行状态的信息。但是,由于探针存储在纯布尔数组中,没有关于对应方法或行的信息。为了检索这些信息,我们需要原始类文件并执行与插桩时相同的控制流分析。由于这是一个确定性过程,我们得到相同的探针位置。有了这些信息,我们现在可以干扰每个指令和方法分支的执行状态。使用类文件中嵌入的调试信息,我们也可以计算行覆盖率。 - 如果我们在分析时使用的类与运行时略有不同,例如不同的方法排序或额外的分支,我们最终会得到不同的探针。例如,索引
i
的探针可能在方法a()
中,而不是在方法b()
中。显然,这将产生随机的覆盖结果。
为什么尝试使用组分析同一类的不同版本时会出现错误
- JaCoCo 总是将一组类作为组进行分析。组用于聚合源文件和包的数据(这两者都可能包含多个类)。在报告 API 中,类通过它们的完全限定名来标识(例如,在 HTML 报告中创建稳定的文件名)。因此,不可能在组中包含两个具有相同名称的不同类。然而,可以分别在不同的组中分析不同版本的类文件,例如,Ant 报告任务可以配置多个组。
ref: https://www.eclemma.org/jacoco/trunk/doc/classids.html