51Testing软件测试论坛
标题:
Java 测试覆盖率 Jacoco 插桩的不同形式总结和踩坑记录(上)
[打印本页]
作者:
草帽路飞UU
时间:
2022-8-3 14:53
标题:
Java 测试覆盖率 Jacoco 插桩的不同形式总结和踩坑记录(上)
本帖最后由 草帽路飞UU 于 2022-8-12 16:46 编辑
一、概述
测试
覆盖率是老生常谈的话题。因为我测试理论基础不是很好,这里就不提需求、覆盖率等内容,直奔主题,本文主要指 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 使用过程的,可能存在于以下几点。
· JDK 版本。
我司现有 JDK6、7、8,但实际上 jdk6 是个分水岭,其他的都基本可以用 JDK8 来适配。
· 构建工具。
我司现有 Maven 构建、ANT 构建,想必有的公司还有用 Gradle 的。
· 部署方式。
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/
[attach]140408[/attach]
下载
后解压文件夹里,目录如下:
这个 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
复制代码
作者:
草帽路飞UU
时间:
2022-8-3 15:02
本帖最后由 草帽路飞UU 于 2022-8-11 16:33 编辑
5.1 这句话的解释
-javaagent
JDK5 之后新增的参数,主要用来在运行 jar 包的时候,以一种方式介入字节码加载过程,如有兴趣自行
百度
。注意后面有个冒号:
/home/admin/Jacoco/Jacocoagent.jar
需要用来介入 class 文件加载过程的 jar 包,想深入了解的,百度 “插桩” 哈。
这是一个 jar 包的绝对路径。
includes=*
这个代表了,启动时需要进行字节码插桩的包过滤,* 代表所有的 class 文件加载都需要进行插桩。
假如你们公司内部代码都有相同的包前缀 :com.mycompany<你可以写成:
includes=com.mycompany.*
output=TCPserver
这个地方不用改动,代表以 TCPserver 方式启动应用并进行插桩。
port=2014
这是 Jacoco 开启的 TCPserver 的端口,请注意这个端口不能被占用。
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。
总结:
这句内容,如下,格式是固定的,只有括号内的东西方可改变,其它尽量不要动,连空格都不要多:
-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=20
注意其他地方基本不用改动。
5.2 war 包方式启动
tomcat 的 war 包方式启动,假设 tomcat 路径为: $CATALINA_HOME= /usr/local/apache-tomcat-8.5.20,我们常用的命令存在于: $CATALINA_HOME\bin下,有 startup.sh 和 shutdown.sh(windows 请自觉改为 bat, 后续不再声明),其实这两个只是封装之后的脚本,底层调用的都是 $CATALINA_HOME\bin\catalina.sh(或者 bat),如图源码:
[attach]140416[/attach]
因此,只需要改动 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"
改完之后如下所示:
[attach]140417[/attach]
改完之后,就可以进行 startup.sh 的启动了,应用启动成功之后,可以在服务器上进行调试,查看 TCPserver 是否真的起来了。
判别方式如下 (该图中是现有的已经开启的服务,所以 IP 和端口跟前面的命令不一样,这点请注意,这里只是为了展示;后续几种方式判别方式相同,不再赘述了哈), 这个端口在应用启动时被占用,在应用关闭时被释放,这个请注意检查:
[attach]140419[/attach]
如此,这个端口已经在监听了,证明这个测试环境已经把 Jacoco 注入进去,那你对该测试环境的任何操作,代码执行信息都会被记录到这个 ip:port 开启的 TCP 服务中。
5.3 Maven 命令的插件启动方式
在我司,有的开发会喜欢用插件方式启动,在代码 pom 文件层级中,运行如下命令:
mvn clean install
mvn tomcat7:run -Dport=xxx
或者还有
mvn clean install
mvn 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 install
export MAVEN_OPTS="-javaagent:JacocoJarPath=includes=*,output=TCPserver,port=2014,address=192.168.110.1"
mvn tomcat7:run -Dport=xxx
export MAVEN_OPTS=""
和
mvn clean install
export MAVEN_OPTS="-javaagent:JacocoJarPath=includes=*,output=TCPserver,port=2014,address=192.168.110.1"
mvn spring-boot:run -Dport=xxx
export 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 参数的子标签,如下代码:
<jvmarg value=”-javaagent:JacocoJarPath=includes=*,output=TCPserver,port=2014,address=192.168.110.1” />
[attach]140420[/attach]
比如我们的启动命令是这样:
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 代理是无异的。
欢迎光临 51Testing软件测试论坛 (http://bbs.51testing.com/)
Powered by Discuz! X3.2