idea 使用jacoco_浅谈Jacoco原理

浅谈Jacoco原理

在上一篇文章中(戳我阅读)我们已经探讨了Jacoco与SonarQube结合后单元测试覆盖率的计算方式,那么这次我们来聊一聊Jacoco获取单元测试覆盖率的原理。 01 单元测试覆盖率工具 目前对于Java来说,主要有Jacoco、JCov、Clover、Cobertura、Serenity、EMMA等工具来获取单元覆盖率,简单介绍如下:
  • Jacoco:目前使用最广泛的开源代码覆盖率工具,它最大的特性是支持通过Java Agent的方式在线对字节码进行插桩,也支持通过离线的方式进行插桩。目前与SonarQube、Jenkins、IDEA、Gradle等工具都有插件集成。
  • JCov:最早的代码覆盖率工具,从Sun JDK的Java 1.1版本就开始使用,到目前也支持离线插桩与通过Java Agent形式的在线插桩。
  • Clover:是Atlassian(Jira与Confluence的那家公司)推出的商业版工具,与免费开源的Jacoco相比,功能自然更多。详细的功能介绍可查看参考文章。此外有开源的OpenClover供大众使用。
  • Cobertura:也是一款使用者较多的覆盖率工具,与Jacoco相比它只有离线插桩模式,且扫描时间比Jacoco要慢一些。
  • EMMA:Jacoco是其替代者,且不支持JDK8,目前使用者逐渐减少。
此外IDEA也提供了自己的代码覆盖率工具,不过为了保证本地覆盖率与服务器扫描后的覆盖率一致,建议在工具中将扫描引擎修改为Jacoco,如下图:272701f192576148a69c302ad7f6c8df.png02 Jacoco覆盖率获取原理8dd6a95cdf7dfc3d492a06ac732f1b28.png

上图展示了代码覆盖率获取的方式,主要可以分为两种:第一种是运行时性能分析(Runtime Profiling),它又可以分为JVMPI(Java Virtual Machine Profiling Interface,JVM性能分析接口)与JVMTI(Java Virtual Machine Tool Interface,JVM工具接口,在JDK6中取代JVMPI与JVMDI,是Java平台调试器架构中最低的层级);第二种是插桩(Instrumentation),其中以插桩为主流。

在插桩方式中又可以分为源码插桩与字节码插桩。源码插桩会在编译前修改代码,在实践过程中就会有这样的问题——流水线编译报错的行数与开发人员看到的行数不一致。而在字节码插桩中,又可以分为On-The-Fly(在线)与Offline(离线)两种模式。在线模式可细分为类加载与代理,而离线模式可细分为替换与注入。在字节码插桩的模式中,Jacoco除了不支持离线替换外,其他都支持。

Jacoco与Cobertura都通过ASM框架来修改字节码,不仅可以修改class文件,还可以修改jar文件。

7a4c85183806dae1673b4773b0357293.gif

ASM是一个通用的Java字节码操作和分析框架。它可以用于修改现有类或直接以二进制形式动态生成类。ASM提供了一些常见的字节码转换和分析算法,可以从中构建自定义复杂转换和代码分析工具。

03 插桩模式 这里详细介绍下On-The-Fly插桩与Offline插桩:
  • On-The-Fly
    • Java Agent:通过在JVM中-javaagent参数指定特定的jar文件启动插桩代理程序,并在加载class时对其进行插桩。
    • Class Loader:通过自定义类加载器在类加载前对class进行插桩。
  • Offline:在编译时对文件进行插桩,生成插过桩的class文件或jar包。其中替代的方式是指插桩会生成新的class,注入的方式是指在原有class文件上进行修改。
两者的主要区别是在线模式是类加载时被插桩(插桩的类不会保留在磁盘中,重要,下文会提到),离线模式是类加载前就被插桩了。相比之下在线模式会使获取覆盖率的过程更简单,并且Jacoco官方认为离线模式有许多问题(并没有说具体有什么问题-_-||),推荐采用在线方式获取覆盖率,如果使用离线模式的话也需要在执行后恢复被插桩的类。 但在线模式有不适合的四大场景:
  • 不支持Java Agent的运行时环境;
  • 无法配置JVM选项的部署;
  • 字节码需要转换为另一个VM,如Android Dalvik VM;
  • 修改字节码过程中与其他Agent冲突;
04PowerMock与Jacoco的冲突 开发人员在编写单元测试代码时,会使用Mockio、PowerMock等工具来做一些mock(模拟对象),其中PowerMock主要用来mock掉一些静态方法或私有方法。 在实践过程中,我们发现许多使用PowerMock的代码明明编写了单元测试,但覆盖率却无法收集,这是怎么回事呢? 原因是PowerMock同样使用了插桩的方式对class文件进行了修改,如果Jacoco采用在线模式的插桩方式,那么插桩后的class并不保存在磁盘中,PowerMock再去插桩的时候仍然是个干净的class。因此如果需要让PowerMock的代码也能收集到覆盖率,那就需要修改Jacoco的插桩模式至离线模式。Pom.xml的demo如下:b7657c44db4388b38c9bce64591fdefe.png

...

org.jacoco

jacoco-maven-plugin

0.8.4

default-instrument

instrument

default-restore-instrumented-classes

restore-instrumented-classes

report

prepare-package

report

target/coverage-reports/jacoco-unit.exec

org.apache.maven.plugins

maven-surefire-plugin

3.0.0-M3

target/coverage-reports/jacoco-unit.exec

...

0 5总结 这回主要和大家简单介绍了主流的代码覆盖率工具以及基本原理,然后针对在实践中出现的代码覆盖率为0的情况,介绍了如何修改Jacoco的插桩模式。那么相信在各种工具的辅助下,我们能够提升代码质量,不断减少缺陷的产生。bbe5033003016f4600a84743057a07ba.png参考文档Java code coverage tools:https://en.wikipedia.org/wiki/Java_code_coverage_tools浅谈代码覆盖率:https://tech.youzan.com/code-coverage/JAVA代码覆盖率工具JaCoCo-原理篇:https://cloud.tencent.com/developer/article/1038055JaCoCo Implementation Design:https://www.jacoco.org/jacoco/trunk/doc/implementation.htmlCode coverage with JaCoCo:https://github.com/powermock/powermock/wiki/Code-coverage-with-JaCoCoComparison of code coverage tools:https://confluence.atlassian.com/clover/comparison-of-code-coverage-tools-681706101.htmlOffline Instrumentation:47c1b2a4a7e09562f2a8d40cc2271987.png

版权声明:本文为weixin_39747577原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。