本文为霍格沃兹测试学院优秀学员关于 Jacoco 的小结和踩坑记录。测试开发进阶学习,文末加群。

一、概述

测试覆盖率是老生常谈的话题。因为我测试理论基础不是很好,这里就不提需求、覆盖率等内容,直奔主题,本文主要指 Java 后端的测试覆盖率。

由于历史原因,公司基本不做 UT,所以对测试来说,咱最关心的还是手工执行、接口执行 (人工 Postman 之类的)、接口自动化、WebUI 自动化对一个应用系统的覆盖度。

本来 Jacoco 已经流行了很多年了,各种文档和帖子已经描述的很完美了,但是多数文章都是针对某一特定形式做了总结和使用。相信很多负责整个公司项目的覆盖率任务的人们来说,还是要一种一种去研究、去应对,入坑、出坑不厌其烦。

也得益于今年上半年一直负责整个公司不同类型的项目的覆盖率统计技术的适配,对不同形式的项目均有一定的了解,在此记录一下,也不让千疮百孔的自己浪费掉这半年的精力,如果说可以帮到别人一星半点,那这篇文章就算是造福了。由于本人能力有限、表达能力有限,如有错误,还请大家多指正。

二、投入覆盖率之前的思路

因为之前了解过一部分 Jacoco 的机制,也知道它提供了很多强大的功能,以满足不同形式的项目。但归根结底,Jacoco 提供了 API,可以让大家屏蔽不同类型的项目带来的困扰。

Jacoco 官方的 Api 示例地址:

https://www.Jacoco.org/Jacoco/trunk/doc/api.html

个人认为,以 Api 的方式来进行操作,可以有以下好处:

可以屏蔽不同方式的构建部署。如果你想把这个功能做成平台,那 API 想必是很好的一种方式。

也就是说,我只需要把 Jacoco 插桩到测试服务器上,暴露 TCP 的 IP 和端口,剩余的提取代码执行数据、生成覆盖率报告,就可以用统一的方式进行就好了。

众所周知,Jacoco 官方提供了 Maven 插件方式、Ant 的 XML 方式,均有对应的 dump 和 report 来进行覆盖率数据的 dump 和报告生成,大家如果有兴趣可以研究一下,这里不赘述。

三、项目梳理

由于我所在的公司是个老牌公司,项目杂乱无章,技术五花八门。至今仍然有跑在 JDK6 上的。所以我个人认为,影响 Jacoco 使用过程的,可能存在于以下几点。

  1. JDK 版本。

我司现有 JDK6、7、8,但实际上 jdk6 是个分水岭,其他的都基本可以用 JDK8 来适配。

  1. 构建工具。

我司现有 Maven 构建、ANT 构建,想必有的公司还有用 Gradle 的。

  1. 部署方式。

Ant、Maven 插件启动、Java -jar 启动、Tomcat 启动 war 包 (打包方式就随便了)

稍后内容也都基于这几种不同实现方式做描述。如果接触项目多的,基本就知道,很多时候测试还是不介入测试环境的发布,这一方面源于开发的不信任,他们认为发布还是要抓在开发自己手里;另一方面也源于测试人员能力的跟不上,至少在我司很多测试人员确实不太懂如何发布(虽然现在慢慢有所缓解,越来越都的测试人员都从开发手中接了过来)。

线上部署、测试部署、开发部署,这几个不同场景,可能用的方式都不同,至少在我接触的项目大都是这样。开发喜欢用插件的方式启动部署,因为快嘛,而且 IDE 也支持,右键运行一下基本在 IDE 就启动了,想想看如果你是开发,在你本地 IDE 里调试的时候,需要打个 war 包然后丢到 Tomcat 里,再启动 Tomcat,你也不太乐意。

四、Jacoco 插桩的本质

废话不多说,步入正题。Jacoco 介入部署过程的本质,就是插桩,至于怎么插桩,跟接入阶段有关系。可以是编译时插桩、也可以是运行时插桩,这就是所谓 Offline 模式和 On-the-fly 模式,我们也不过多于纠结,我们选择了 on-the-fly 模式。

所以归结到本质,Jacoco 的 on-the-fly 模式的插桩过程,其实就是在测试环境部署的时候,让 Jacoco 的相关工具,介入部署过程,也就是介入 class 文件的加载,在加载 class 的时候,动态改变字节码结构,插入 Jacoco 的探针。

本质:Jacoco 以 TCPserver 方式进行插桩的本质,就是如果应用启动过程中,进行了 Jacoco 插桩,且成功了。它会在你当前这个启动服务器中,在一个端口{$port}上,开启一个 TCP 服务,这个 TCP 服务,会一直接收 Jacoco 的执行覆盖率信息并传到这个 TCP 服务上进行保存。

既然是个 TCP 服务,那 Jacoco 也提供了一种以 API 的方式连接到这个 TCP 服务上,进行覆盖率数据的 dump 操作。(细节可能描述的不是很精确,但差不多就是这么个过程。这个 TCP 服务,在你没有关闭应用的时候,是一直开着的,可以随时接受连接)

再本质一点,就是介入下面这个命令的启动过程:

java -jar 

那问题就好办了,一种一种来对应起来。

五、不同形式的插桩配置

提到介入启动过程,那就免不了提一下一个 jar 包。

Jacocoagent.jar下载地址:

https://www.eclemma.org/Jacoco/

下载后解压文件夹里,目录如下:

这个 Jacocoagent.jar, 就是启动应用时主要用来插桩的 jar 包。

请注意不要写错名称,里面有个很像的 Jacocoant.jar,这个 jar 包是用 ant xml 方式操作 Jacoco 时使用的,不要混淆。

以测试环境部署在 Linux 服务器上为例,如果想在 Windows 上测试也可以,把对应的值改成 Windows 上识别的即可。

假设 Jacocoagent.jar 的存放路径为:/home/admin/Jacoco/Jacocoagent.jar

以下都以 $JacocoJarPath 来替代这个路径,请注意这个路径不是死的,你可以修改。

依然是基于上述的几种不同方式,那我们针对不同形式·做插桩,也就是改变这几种不同形式的底层启动原理,也就是改动不同方式的 java 的启动参数,这对每一种启动方式都不太一样。但是改动 Java 启动参数本质也是一样的,就是在 java -jar 启动的时候,加入 -javaagent 参数。

-javaagent:$JacocoJarPath=includes=*,output=TCPserver,port=2014,address=192.168.110.1"

换成实际的信息为如下,请注意替换真实路径,这一句是需要介入应用启动过程的主要代码,针对每种不同的部署方式,需要加到不同的地方。

-javaagent:/home/admin/Jacoco/Jacocoagent.jar=includes=*,output=TCPserver,port=2014,address=192.168.110.1

5.1 这句话的解释

  1. -javaagent

JDK5 之后新增的参数,主要用来在运行 jar 包的时候,以一种方式介入字节码加载过程,如有兴趣自行百度。注意后面有个冒号:

  1. /home/admin/Jacoco/Jacocoagent.jar

需要用来介入 class 文件加载过程的 jar 包,想深入了解的,百度 “插桩” 哈。这是一个 jar 包的绝对路径。

  1. includes=*

这个代表了,启动时需要进行字节码插桩的包过滤,* 代表所有的 class 文件加载都需要进行插桩。

假如你们公司内部代码都有相同的包前缀 :com.mycompany

includes=com.mycompany.*
  1. output=TCPserver

这个地方不用改动,代表以 TCPserver 方式启动应用并进行插桩。

  1. port=2014

这是 Jacoco 开启的 TCPserver 的端口,请注意这个端口不能被占用。

  1. address=192.168.110.1

这是对外开发的 TCPserver 的访问地址。可以配置 127.0.0.1, 也可以配置为实际访问 IP。

配置为 127.0.0.1 的时候,dump 数据只能在这台服务器上进行 dump,就不能通过远程方式 dump 数据。配置为实际的 IP 地址的时候,就可以在任意一台机器上 (前提是 IP 要通,不通都白瞎),通过 Ant XML 或者 API 方式 dump 数据。举个栗子:

我如上配置了 192.168.110.1:2014 作为 Jacoco 的 TCPserver 启动服务,那我可以在任意一台机器上进行数据的 dump,比如在我本机 Windows 上用 API 或者 XML 方式调用 dump。

如果我配置了 127.0.0.1:2014 作为启动服务器,那么我只能在这台测试机上进行 dump,其他的机器都无法连接到这个 TCPserver 进行 dump。

  1. 总结:

这句内容,如下,格式是固定的,只有括号内的东西方可改变,其它尽量不要动,连空格都不要多:

-javaagent:(/home/admin/Jacoco/Jacocoagent.jar)=includes=(*),output=TCPserver,port=(2014),address=(192.168.110.1)

比如我可以改成其他的:

-javaagent:/home/admin/Jacoco_new/Jacocoagent.jar=includes=com.company.*,output=TCPserver,port=2019,address=192.168.110.111

注意其他地方基本不用改动。

5.2 war 包方式启动

tomcat 的 war 包方式启动,假设 tomcat 路径为: $CATALINA_HOME= /usr/local/apache-tomcat-8.5.20,我们常用的命令存在于: $CATALINA_HOMEbin下,有 startup.sh 和 shutdown.sh(windows 请自觉改为 bat, 后续不再声明),其实这两个只是封装之后的脚本,底层调用的都是 $CATALINA_HOMEbincatalina.sh(或者 bat),如图源码:

因此,只需要改动 catalina.sh 中的启动参数即可。

前面提到过,主要改动主要是改动 java -jar,tomcat 是通过一个 JAVA_OPTS 参数来控制额外的 java 启动参数的,我们只需要在合适的地方把上面的启动命令追加到 JAVA_OPTS 即可打开 catalina.sh,找到合适的地方修改 JAVA_OPTS 参数:

理论上,任何地方修改 JAVA_OPTS 参数均可,但我们实验过后,在以下位置加入,是一定可以启动成功的,当然您也可以尝试其他位置。

JAVA_OPTS="$JAVA_OPTS -Dorg.apache.catalina.security.SecurityListener.UMASK=`umask`"

源脚本中有这个注释掉的地方,我们在下方修改 JAVA_OPTS,在其下方,加一句:

JAVA_OPTS="$JAVA_OPTS -javaagent:$JacocoJarPath=includes=*,output=TCPserver,port=2014,address=192.168.110.1"

改完之后如下所示:

改完之后,就可以进行 startup.sh 的启动了,应用启动成功之后,可以在服务器上进行调试,查看 TCPserver 是否真的起来了。

判别方式如下 (该图中是现有的已经开启的服务,所以 IP 和端口跟前面的命令不一样,这点请注意,这里只是为了展示;后续几种方式判别方式相同,不再赘述了哈), 这个端口在应用启动时被占用,在应用关闭时被释放,这个请注意检查:

如此,这个端口已经在监听了,证明这个测试环境已经把 Jacoco 注入进去,那你对该测试环境的任何操作,代码执行信息都会被记录到这个 ip:port 开启的 TCP 服务中。

5.3 Maven 命令的插件启动方式

在我司,有的开发会喜欢用插件方式启动,在代码 pom 文件层级中,运行如下命令:

mvn clean installmvn tomcat7:run -Dport=xxx

或者还有

mvn clean installmvn spring-boot:run -Dport=xxx

这两套命令,本质上没什么差别,只是运行插件不一样,具体用什么命令,如果不清楚,最好是跟开发请教一下。

他们的意思是,在当前代码的 pom 文件层级运行,意思是通过 maven 的 tomcat 插件启动这个服务,这个服务启动在端口 xxx 上,注意这个端口是应用的访问端口,和 Jacoco 的那个端口不是一回事。

对这种方式注入 Jacoco,也是可以的。这种可以不用修改任何的配置文件,只需要在你启动的时候,临时修改变量就行了。这种方式改变 java 的启动参数方式是这样:

export MAVEN_OPTS="-javaagent:$JacocoJarPath=includes=*,output=TCPserver,port=2014,address=192.168.110.1"

这句命令加在哪里呢?就是 run 之前。为什么呢,因为这样一改,你的所有的 mvn 命令都会生效,但其实我们只想介入启动过程。因此,前面提到的两套启动命令,就可以改成如下方式:

mvn clean installexport MAVEN_OPTS="-javaagent:$JacocoJarPath=includes=*,output=TCPserver,port=2014,address=192.168.110.1"mvn tomcat7:run -Dport=xxxexport MAVEN_OPTS=""

mvn clean installexport MAVEN_OPTS="-javaagent:$JacocoJarPath=includes=*,output=TCPserver,port=2014,address=192.168.110.1"mvn spring-boot:run -Dport=xxxexport MAVEN_OPTS=""

当然,你的 run 命令,也可能是其他变种,比如:nohup mvn …. & 这种后台启动的方式,也是可以的。最后修改为 "" 是因为担心对后续的 mvn 命令产生影响,其实如果你切换了 terminal 窗口,这个临时变量就会失效,不会对环境造成污染。

如果应用启动成功了,就可以按照前面的方式,netstat 叛别一下 TCP 服务是否真的启动。

如果你设置了这个变量的位置不对,那你用 mvn 命令的时候,可能会出现如下的异常:

java.net.BindException: Address already in use: bind

这时候,就需要去检查一些,你配置的 Jacoco 端口是不是在启动应用服务时已经被占用。或者你临时设置了 MAVEN_OPTS 这个变量,启动之后又没有改回来,然后接着运行了 mvn 命令,这时候也会出现这种错误。这里请务必关注。

提一句题外话,ANT 的方式是不是也可以通过临时修改 ANT_OPTS 参数进行启动 (因为 ANT 和 MAVEN 本是一家子吗,我猜底层可能差异不是很大),我不曾做尝试,有兴趣的可以尝试下。

5.4 ANT 构建,通过 XML 配置文件启动

这种方式可能实现启动应用的阶段不同,但大都配置在 build.xml 里,这里请根据不同的项目做不同的适配。

它的原理是,在 Ant 的启动 target 中,有个 的标签,给她增加一个 jvmarg 参数的子标签,如下代码:

比如我们的启动命令是这样:

ant -f build.xml clean  build  startJetty

以此启动之后,将会注入 Jacoco 的代理,最终可以按照上面的方式判断端口是否启动。

5.5 java -jar 方式启动

这种最简单直接:

java -javaagent: $JacocoJarPath=includes=*,output=TCPserver,port=2014,address=192.168.110.1 -jar  xxxxxxxxxx.jar 

注意,javaagent 参数,一定要在 jar 包路径之前,尽量在-jar 之前,不然可能不会生效。请注意 java -jar 命令的使用方式,在 jar 包前面传进去的是给 jvm 启动参数的,在 jar 包之后跟的是给 main 方法的。

启动后,依然按照前面的方式判断是否启动了监听端口。

5.6 启动之后

启动之后,就进行测试就可以了,跟平常不注入 Jacoco 代理是无异的。

想继续观看精彩内容可看下文。

(文章来源于霍格沃兹测试学院)

sonar覆盖率怎么统计的_实战|Java 测试覆盖率 Jacoco插桩的不同形式总结和踩坑记录(上)...相关推荐

  1. mvn exec: java_实战|Java 测试覆盖率 Jacoco插桩的不同形式总结和踩坑记录(下)

    本文为霍格沃兹测试学院优秀学员关于 Jacoco 的小结和踩坑记录.测试开发进阶学习,文末加群. 六.注意事项汇总 修改 JAVA_OPTS 参数时,如果位置不对,可能造成代理无法启动. java - ...

  2. Python打包工具Pyintealler打包py文件为windows exe文件过程及踩坑记录+实战例子

    Python打包工具Pyintealler打包py文件为windows exe文件过程及踩坑记录+实战例子 目录 Python打包工具Pyintealler打包py文件为windows exe文件过程 ...

  3. 使用Java读取 “Python写入redis” 的数据踩坑记录

    https://my.oschina.net/u/2338224/blog/3061507 使用Java读取 "Python写入redis" 的数据踩坑记录 https://seg ...

  4. 安卓 Native+Flutter 应用开发入门资料、亲身实战及踩坑记录

    安卓 Native+Flutter 应用开发实战及踩坑记录,练手入门项目:FluLearn 入门资料 第三方共享包检索(国内).第三方共享包检索(国外) Flutter开发环境搭建(中文版).Flut ...

  5. 微信退款 java工具类,微信支付中退款踩坑记录

    首先附上微信支付的开发者文档 其实这里所说的踩坑记录,无非就是微信在开发者文档上的写不太明确,也没有比较官方的demo,在此列出一个可行的demo,供大家下载使用. 主要问题就是在这几步解密上 微信的 ...

  6. Java 线上惨痛踩坑记录,你也一定遇到过

    线上问题年年有,今年特别多.记几次线上惨痛的踩坑记录,希望大家以史为鉴. 1. 包装类型自动解箱导致空指针异常 public int getId() { Integer id = null; retu ...

  7. Java线上惨痛踩坑记录,你也一定遇到过

    线上问题年年有,今年特别多.记几次线上惨痛的踩坑记录,希望大家以史为鉴. 1. 包装类型自动解箱导致空指针异常 public int getId() {Integer id = null;return ...

  8. android应用测试与调试实战_实战 | Java 服务端和 Android 端手工测试覆盖率统计的实现...

    本文为霍格沃兹测试学院优秀学员关于后端和 App 端手工测试覆盖率的学习笔记.测试开发进阶学习,文末加群. 一.前言 代码测试覆盖率工具流行了这么多年,已经有很多成熟方案比如 Jacoco,我司近一段 ...

  9. 测试用例设计_如何提高测试覆盖率

    前言 说到测试用例的设计,我想每个有过测试经历的测试工程师都会认为很简单,不就是:按需求或概要设计,得到软件功能划分图,然后据此按每个功能,采用等价类划分.临界值.因果图等方法来设计用例就行了. 但事 ...

最新文章

  1. 开源项目贡献者_如何吸引新的贡献者加入您的开源项目
  2. php 脚本 fpm缓存,PHP生命周期及fpm(FastCGI进程管理器)的运作方式
  3. cv2.dnn读取模型报错
  4. JavaEE是什么?
  5. matlab 并联机械臂_MATLAB robot toolbox 机械臂轨迹规划
  6. 软考网络管理员存储容量计算相关问题
  7. 带你了解FPGA(1)--一些需要了解的概念
  8. 初学者python笔记(装饰器、高阶函数、闭包)
  9. Maven学习(三)-----Maven本地资源库
  10. MT6763/MT6763T处理器参数比较,MT6763设计资料参考
  11. PSAM卡、SAM卡、SIM卡
  12. 计算机电子表格操作步骤,Excel电子表格操作基本步骤.doc
  13. PAT 1160 Forever
  14. 日暮途远,故吾倒行而逆施之.
  15. 数据库实验——简单数据库应用系统设计与实现
  16. 图片太多怎么办?教你怎么批量压缩图片大小
  17. 数字经济时代,达尔文平台助力广告投放走向数智化
  18. 论人工智能与软件工程的关系
  19. Servlert 下 (HttpServletRequest 类和 HttpServletResponse 类)
  20. ios应用程序加载分析(一)

热门文章

  1. linux中dns超时时间,Linux DNS timeout, attempts.---DNS超时,重试的配置
  2. java关键字:volatile
  3. 12篇文章带你逛遍主流分割网络
  4. springmvc结合freemarker,非自定义标签
  5. DataTable转Csv,Excel(转)
  6. [面试]如何写testcase?
  7. 系统查找存储过程和触发器
  8. java 11_Java 11 正式发布!8年免费使用!
  9. 应用id_科普贴:什么是OpenID、AppID 、用户ID等各种ID?
  10. android studio 多个项目管理,Android Studio之同一应用创建多个Activity(一)