-
java代码覆盖率jacoco原理及案例分析
-
目录:https://blog.csdn.net/liuxueyi521/article/details/87898581
-
实战案例:视频内容1:https://edu.csdn.net/course/detail/19104
-
实战案例:视频内容2:http://edu.51cto.com/course/17028.html?source=so
- 1、On-the-fly方式
必须在运行时收集覆盖率信息。在使用 java agent加载类期间(即 jacoco agent ),用是的 on-the-fly方式,为此,JaCoCo创建原始类定义的检测版本。使用Java代理在类加载期间即时进行检测。
收集覆盖信息有几种不同的方法。对于每种方法,已知不同的实现技术。下图概述了JaCoCo使用的技术:
字节代码检测非常快,可以用纯Java实现并与每个Java VM一起使用。可以将带有Java代理挂钩的实时检测添加到JVM,而无需对目标应用程序进行任何修改。
Java代理程序钩子至少需要1.5 JVM。使用调试信息(行号)编译的类文件允许突出显示源代码。不幸的是,一些Java语言结构被编译为字节代码,产生意外的突出显示结果,尤其是在隐式生成的代码(如默认构造函数或finally语句的控制结构)的情况下。
- 2、Offline方式
根据class文件,生成新的class文件,并在其中插入探针。这种方式不需要jvm的支持。
- 3、覆盖代理隔离
Java代理程序由应用程序类加载器加载。因此,代理的类与应用程序类一样存在于同一名称空间中,这可能导致冲突,特别是与第三方库ASM的冲突。因此,JoCoCo构建将所有代理类移动到一个唯一的包中。
JaCoCo构建jacocoagent.jar
使用 org.jacoco.agent.rt_<randomid>
前缀重命名into类中包含的所有类 ,包括所需的ASM库类。标识符是从随机数创建的。由于代理不提供任何API,因此重命名不应影响任何人。此技巧还允许使用JaCoCo验证JaCoCo测试。
- 4、最低Java版本
JaCoCo需要Java 1.5。
用于实时检测的Java代理机制随Java 1.5 VM一起提供。使用Java 1.5语言级别进行编码和测试比使用旧版本更有效,更不容易出错并且更有趣。JaCoCo仍然允许针对为这些编译的Java代码运行。
- 5、字节码操作
检测需要修改和生成Java字节代码的机制。JaCoCo在内部使用ASM库来实现此目的。即jacoco工具使用ASM技术来实现插入字节码,因此jacoco也包含了ASM需要的库。
实现Java字节代码规范将是一项广泛且容易出错的任务。因此,应该使用现有的库。该 ASM库是轻量级的,易于使用,并且在内存和CPU使用方面非常有效。它是积极维护的,包括巨大的回归测试套件。其简化的BSD许可证已获得Eclipse Foundation批准,可用于EPL产品。
- 6、Java类标识
在运行时加载的每个类都需要一个唯一的标识来将coverage数据与之关联。JaCoCo通过原始类定义的CRC64哈希码创建此类标识。
在多类加载器环境中,类的普通名称不能明确地标识类。例如,OSGi允许在同一个VM中加载同一类的不同版本。在复杂的部署方案中,测试目标的实际版本可能与当前的开发版本不同。代码覆盖率报告应保证所呈现的数字是从有效的测试目标中提取的。类定义的哈希码允许区分类的类和版本。CRC64散列计算简单而快速,从而产生小的64位标识符。
类加载器可能会加载相同的类定义,这将导致Java运行时系统的不同类。对于覆盖分析,这种区别应该是无关紧要的。类定义可能会被其他基于仪器的技术(例如AspectJ)改变。在这种情况下,哈希码将发生变化,身份将丢失。另一方面,基于以某种方式改变的类的代码覆盖率分析将产生意外的结果。CRC64代码可能产生所谓的冲突,即为两个不同的类创建相同的哈希码。尽管CRC64在加密方面并不强大,并且可以轻松计算碰撞示例,但对于常规类文件,碰撞概率非常低。
- 7、覆盖率运行时依赖性
检测代码通常依赖于覆盖运行时,后者负责收集和存储执行数据。JaCoCo仅在生成的检测代码中使用JRE类型。
在使用自己的类加载机制的框架中,为所有已检测的类提供运行时库可能是一项痛苦或不可能完成的任务。由于Java 1.6 java.lang.instrument.Instrumentation
具有扩展bootsstrap加载器的API。由于我们的最低目标是Java 1.5,因此JaCoCo仅通过官方JRE API类型将检测类和coverage运行时分离。检测类通过该Object.equals(Object)
方法与运行时进行通信 。检测类可以使用以下代码检索其探测数组实例。请注意,仅使用JRE API:
- 对象访问= ... //检索实例
- Object [] args = new Object [ 3 ];
- args [ 0 ] =很长。valueOf (8060044182221863588 ); // 班级号
- args [ 1 ] = “com / example / MyClass” ; // 班级名称
- args [ 2 ] = 整数。valueOf (24 ); //探测计数
- 访问。等于(args );
- boolean [] probes = (boolean [])args [ 0 ];
最棘手的部分发生在第1行,并未在上面的代码段中显示。equals()
必须获得通过其方法提供对覆盖运行时的访问的对象实例。到目前为止,已经实施和测试了不同的方法:
SystemPropertiesRuntime
:此方法将对象实例存储在系统属性下。此解决方案违反了系统属性必须仅包含java.lang.String
值的合同,因此会导致依赖此定义的应用程序(例如Ant)出现问题。LoggerRuntime
:这里我们使用共享java.util.logging.Logger
并通过logging参数数组而不是equals()
方法进行通信。coverage运行时注册一个自定义Handler
来接收参数数组。这种方法可能会破坏安装自己的日志管理器的环境(例如Glassfish)。URLStreamHandlerRuntime
:此运行时URLStreamHandler
为“jacoco-xxxxx”协议注册a 。检测类打开此协议的连接。返回的连接对象是通过其equals()
方法提供对coverage运行时的访问的对象 。但是,要注册协议,运行时需要访问java.net.URL
该类的内部成员。ModifiedSystemClassRuntime
:此方法通过检测向现有JRE类添加公共静态字段。与上述其他方法不同,这仅适用于Java代理处于活动状态的环境。InjectedClassRuntime
:此方法定义了java.lang.invoke.MethodHandles.Lookup.defineClass
Java 9中引入的新类。
从版本0.8.3开始,JaCoCo Java代理实现 InjectedClassRuntime
在JRE 9及更高版本上运行时使用在bootstrap类加载器中定义新类,否则用于 ModifiedSystemClassRuntime
向现有JRE类添加字段。从版本0.8.0开始,字段被添加到类中 java.lang.UnknownError
,版本0.5.0 - 0.7.9正在向类添加字段java.util.UUID
,与其他代理冲突的可能性更大。
- 8、内存使用情况
应该可以对具有几千个类或十万行代码的大型项目进行覆盖率分析。为了在合理的内存使用情况下允许这种情况,覆盖率分析基于流模式和“深度优先”遍历。
巨大覆盖率报告的完整数据树太大,无法放入合理的堆内存配置中。因此,覆盖率分析和报告生成被实现为“深度优先”遍历。这意味着在任何时间点只有以下数据必须保存在工作内存中:
- 目前正在处理的单个类。
- 本课程所有家长(包,小组)的摘要信息。
- Java元素标识符
Java语言和Java VM对Java元素使用不同的String表示格式。例如,当Java中的类型引用读取时 java.lang.Object
,VM引用的类型相同 Ljava/lang/Object;
。JaCoCo API仅基于VM标识符。
直接使用VM标识符不会在运行时导致任何转换开销。有几种基于Java VM的编程语言可能使用不同的符号。因此,特定转换应仅在用户界面级别进行,例如在报告生成期间。
- 9、JaCoCo实施的模块化
JaCoCo在几个模块中实现,提供不同的功能。这些模块作为OSGi包提供,带有适当的清单文件。但是OSGi本身并没有依赖性。
使用OSGi包可以在开发时和运行时在OSGi容器中实现良好定义的依赖关系。由于OSGi没有依赖关系,因此捆绑包也可以像常规JAR文件一样使用。