jacoco在统计覆盖率时通过类标识符(class id)来标识一个类,接下来我们了解下Jacoco类标识符
什么是类标识符,它们是如何创建的
类标识符是一个64位整数,例如0x638e104737889183,是通过计算原始的class文件的CRC64产生的。
类标识符有什么用
类标识符用于识别Java类,在被加载的类运行时产生并存储在*.exec文件中,在分析的时候,比如在报告产生的时候,会通过类标识符把待分析的类和执行数据关联起来。
Jacoco类标识符的优点及缺点
优点:当服务部署了不同版本的应用,或者引用了不同版本的依赖时,对应的类标识符不同。类标识符时jacoco运行和*.exec产生的先决条件。
缺点:标识类的不同版本,在运行和分析时可能出现问题,可能导致执行数据不能与待分析的类关联起来,甚至产生的覆盖率结果为0。
当问题产生时如何发现
最明细的特征是某个类虽然被执行了,但覆盖率为0,可能就是由于运行时和分析时用的类标识符不一致导致的。
如何会导致产生不同的类标识符
只有字节码完全一致的类的标识符才相同,导致产生不同的字节码可能有几种原因:
通过不同的IDE编译代码:
1. 不同类型的编译器((e.g. Eclipse vs. Oracle JDK)
2. 不同版本的编译器
3. 不同的编译配置(e.g. debug vs. non-debug)
post-processing 类处理器 (obfuscation, AspectJ, etc.)可以改变类。
尽管类文件相同,但由于在jacoco agent之前启用了其它代理,或者特殊的类加载器pre-process类文件,导致jacoco agent 运行时的类不同,例如:
1. mock框架
2. 应用服务器
3. 持久化框架
对于运行时变更的类该如何处理
假如类在运行时发生了变化,还是可以采取些措施保证jacoco正常运行的。
- 如果采用了其它的java代理,保证jacoco代理优先指定,这样jacoco代理就可以接受原始字节码。
- 通过指定classdumpdir,jacoco agent可以dump出运行的类,通过dump类来进行分析。只有被加载的类才会被dump出来,没有执行的类不会在报告中出现。
- 通过offline插桩,这样就避免了任何运行时修改。这种模式产生的报告基于原始的class,而非插桩后的类。
为什么Jacoco不同仅仅通过类名称来识别类
我们需要了解jacoco如何统计覆盖率的,才能回答这个问题:
Jacoco通过探针来追踪代码执行,探针是插入到类中的字节码桩,用来标记执行的代码并报告给jacoco执行器,这个过程被称为插桩。为了保证占用较少的时间,只有较少的关键地方插入探针,通过方法控制流分析决定插桩的地方,这样被插桩后的类将会产生一系列布尔标识来表示探针是否被执行,*.exec中只会为每一个类标识符存储对应的布尔数组。
分析的时候,比如产生报告,.exec文件只用来获取探针的执行状态,并没有对应的方法的行信息。为了获取方法和行信息,需要执行与插桩时相同的控制流分析,由于这是一个确定性的过程,所以我们可以获取相同的探针地址,通过这些信息,我们可以得到每个方法的指令覆盖和分支覆盖,通过类文件的debug信息,我们可以计算行覆盖。
如果运行时和分析时运用的类不同,比如方法顺序不同或分支不同,这将产生不同的探针,比如第i个探针在方法a中不在方法b中,这将导致产生的覆盖率不准确。
当分析同一个同一个类不同版本时为什么会报错
Jacoco通常按组分析代码,并聚合数据,覆盖率报告的接口通过类的名称来区分,这样在同一个组中不能包含有相同名称的类。在不同组中可以包含相同名称的类,比如可以ant可以配置多个分组。