小记 Java 服务端和 Android 端手工测试覆盖率统计的实现
关于后端和App端手工测试覆盖率的总结
一、前言
代码覆盖率工具已经流行了这么多年,其他公司已经用熟、用烂了,我司近一段时间开始了这方面的摸索,很荣幸这个任务到了我的手里,于是乎就开始踩坑之旅。
之前已经搞定了Java后端的覆盖率统计,当然了我们没有UT,毫无疑问使用的还是On-the-fly模式,最近几天开始了App端的手工测试覆盖率统计之旅,中间出现了一些坑,有的很快就搞定了,有的也多多少少占用了不少时间,在此记录一下,免得以后忘记,也抛砖引玉,一起探讨下。
二、前期思绪整理
2.1 关于Java端
我们都知道,jacoco流行了这么多年,无疑是解决Java后端覆盖率的不二之选(至于Emma,当然也是先驱,只不过我们没有选择)。分析了我司关于Java端项目的现状,总结了如下几点:
- 老牌的项目,使用Java6,框架么就是ssh咯,一般来说都是Ant构建,jetty部署
- 稍微新一点的项目,使用Java8,主框架SSM,一般直接maven构建,maven的tomcat插件部署或者打成war包用tomcat部署
- 再新一点的项目,使用Java8,就是Springboot 和Spring Cloud框架,maven的springboot插件部署或者war包用tomcat部署,或者打成jar包,直接用Java命令启动
终其一点,还是要用On-the-fly模式,方便嘛
2.2 关于Android端
基本清一色Android Studio开发,gradle构建打包
2.3 关于覆盖率环境的部署和收集
研究过jacoco的都知道,它支持多种方式的部署和报告生成。但总结到一点,还是修改启动时候的jvm参数,-Javaagent配置一下,但是不同的构建工具有其封装方式,Ant和Maven也是shell或者bat脚本,对一系列操作的封装,底层依然是Java命令的调用。
所以一个万能的方式,就是修改ant和mvn的脚本,直接修改其脚本中用到的JAVA_OPTS参数,但是弊端也很明显,就是会对所有的ant和mvn命令生效,因此并不可取。
所以,还是选择了针对特定构建和打包方式的启动适配。
比如:
- Ant启动
可以修改build.xml,在启动部署的target(比如我司是用startJetty)中配置一个jvmarg,设置成需要启动的jacoco配置,如下所示:
<target name="startJetty">
<mkdir dir="../logs" />
<mkdir dir="../heapdump" />
<java classname="com.tianque.JettyProduction" spawn="true" classpath="${classes.dir}" classpathref="all.lib" fork="true">
<arg value="${port}" />
<arg value="${listenerport}" />
<arg value="${path}" />
<arg value="${rootdir}" />
<arg value="${openJob}" />
<jvmarg value="-Javaagent:/home/admin/jacocoagent/jacocoagent.jar=includes=*,output=tcpserver,port=8888,address=192.168.1.105" />
- Maven启动
mvn启动的时候,看过mvn脚本的大概知道,他提供了一个MAVEN_OPTS这个环境变量,可以临时修改启动参数,因此对mvn tomcat7:run 或者mvn springboot:run这种方式部署项目,可以选择临时修改它,来完成jacoco的注入,如下所示:
export MAVEN_OPTS="-Javaagent:$jacocoJarPath=includes=*,output=tcpserver,port=2014,address=192.168.110.1"
之后,在运行mvn xx:run(需要后台启动的话,加上nohup 也无可厚非),这样基本可以注入jacoco agent了。
部署之后,再设置
export MAVEN_OPTS=""
就可以恢复,或者换个terminal窗口,也可以恢复,这样不用动任何的后端代码,也不会对当前的服务器环境造成污染,开发在测试环境部署的时候,有很多会喜欢这种方式。
- jar启动
这个就更简单了,因为这个是最原始也是直接的Java应用启动方式
Java -Javaagent: $jacocoJarPath=includes=*,output=tcpserver,port=2014,address=192.168.110.1 -jar xxxxxxxxxx.jar
此处需要注意-Javaagent这个参数的放置位置,因为放在.jar包之前是针对jvm设置,放到.jar之后是针对jar包里启动的main方法args的参数,位置放错,就会注入jacoco失败
- tomcat启动
这种方式,就是改变tomcat的启动文件,catalina.sh或者catalina.bat中的JAVA_OPTS参数,这个网上文档有很多,不再多说
2.4 关于覆盖率数据的收集和报告生成
这也有很多方式,比如用ant的build.xml和maven的jacoco插件来收集和生成报告,这个网上也有很多,不再多议。
但其实看过官方文档的就知道,其实jacoco自己提供了api,来收集和生成报告,这是比较原始的方式,也是最好用的方式。
Jacoco给出的Api示例如下:
Jacoco官方的Api示例
地址: https://www.jacoco.org/jacoco/trunk/doc/api.html
为了降低测试部对这些知识的学习成本,我选择了统一的方式,用api来收集和生成报告,这样以来,测试人员需要介入的地方就只剩下了带有jacoco agent的测试环境部署,剩余的事情,交给我就行了。
因此,花了点时间做了个覆盖率的收集和生成报告的平台,这个后面会简单说下,不是为了炫耀也不是为了拿绩效,就是单纯想减少学习成本(这样大家就不会抱怨还要学习ant的知识啦、maven的知识啦、可能还需要学习Java的知识啦,虽然这是好的,但是我相信还是有很多人不愿意投入的)
2.5 关于App端覆盖率的收集和报告生成
关于App端的覆盖率收集和报告生成,社区有很多帖子介绍,我稍后也会提到,给我帮助很多。
主要思路,就是先拿到手工测试的覆盖率数据,因为这里是用offline的方式生成的,这一点好像途径并不多,但比较麻烦,还是要懂Android端的开发知识(比如AS的使用、gradle的配置和任务执行、Android工程代码的结构、甚至还要懂一些Groovy的语法等等),因为要涉及到对工程的一部分修改和打包。
拿到覆盖率数据之后,就可以生成报告了,那按照之前的说法,也有两种方式:
- 使用gradle的jacoco plugin,它给出了生成报告的任务,这些涉及到的帖子里都有
- 有了前面的覆盖率收集和报告生成平台,既然收集不需要了,那么报告生成是可以复用的嘛,于是乎做了下对Gradle工程的适配,只要上传app端的exec文件,就可以生成报告。
我选择了后者,还是那句话,因为如果用前者,那势必要给测试人员讲解Android开发一些相关知识(生成报告的时候,还要涉及到源码和class文件的配置,以及涉及到多模块的收集配置)
三、感谢
在摸索的过程中,感谢以下文章给了灵感和指引,前人的摸索和努力,在很大程度上减少了我们踩坑的概率。
Android 手工测试的代码覆盖率
地址: https://testerhome.com/topics/2510
浅谈代码覆盖率
地址: https://testerhome.com/articles/16981
Android 手工测试代码覆盖率增强版
地址:https://testerhome.com/topics/2524
Jacoco 统计 Android 代码覆盖率 [instrument 方式]
地址: https://testerhome.com/topics/16376
Android jacoco 代码覆盖率测试入门
地址: https://testerhome.com/topics/17066
当然了还有很多,就不在此一一列举了,感谢社区有这么多的好文章,谢谢大家的默默付出!
[定制触发条件] jacoco 统计 Android 代码覆盖率
地址: https://testerhome.com/articles/17546
最最值得感谢的是社区的[AngryTester]
他有个开源项目,地址如下:
https://github.com/AngryTester/jacoco
我做的平台,集成了这个开源项目,当然了也做了不少改动,但还是站在了巨人的肩膀上。在搞后端覆盖率的时候,当时也碰到很多问题,也跟这位大佬邮件交流了几次,人很nice,回答的也很仔细,给了我莫大的帮助!感谢~~~
再次感谢这些默默付出的人们!!!
四、App端覆盖率进行时遇到的坑
按照上述的这些精彩文章里,对Android端的代码覆盖率统计的介绍里,照理来说应当一气呵成了,但还是遇到了一些坑。
比如:
在收集到手机端的覆盖率数据之后,传到后台,开始生成的时候,一直报以下错误:
[2019.09.17 14:45:59][ERROR] c.a.p.t.j.ReportGenerator - IO异常,Cannot read execution data version 0x1006. This version of JaCoCo uses execution data version 0x1007.
[2019.09.17 14:45:59][ERROR] c.a.p.e.GlobalExceptionHandler -
com.administrator.platform.exception.base.BusinessValidationException: 覆盖率的Jacoco版本不匹配:Cannot read execution data version 0x1006. This version of JaCoCo uses execution data version 0x1007.
经过分析,这是覆盖率数据和当前所用生成报告的jacoco版本不一致,我用的是0.8.1-snapshort版本,它支持的版本是0X1007,这个从jacoco的源码可以看到:
很显然,是App端生成的覆盖率数据版本低了。
可是按照上面的文档,jacoco中的toolVersion已经设置成0.8.1了啊,它肯定支持的也是0X1007啊。
而且查了官方给出的文档,对应关系如下:
地址在: https://github.com/jacoco/jacoco/wiki/ExecFileVersions
五、这个坑是怎么过去的
截止到此时,坑已经出现了,那肯定要填,于是乎就开始了一系列排查问题过程:
5.1 首先怀疑的是哪个toolVersion,于是乎做了以下尝试
第一步,改版本 – 往高了调和往低了调都不行,依然是无法解析
第二步,把这个版本删除(设置为空字符串””) – 我去,覆盖率数据依然能出来,这说明这个plugin中设置的这个toolVersion对这个apk生成的时候不生效啊。
第三部,把那个testCoverageEnabled 设置成false,很好,报错了,ClassNotFound,这就对了,于是乎有了后续的步骤
5.2 找了移动端的开发负责人
询问这个jacoco的机制,结果跟社区的文章里和网上能查到的大都差不多。
但是他给了另一个提示:[我们这个jacoco插件当前的运行方式是编译期,而不是运行期],
因为在此之前,我跟他描述了我们的需求,基本确定了我们的生成方式,是运行期而不是编译期,既然是在apk的代码里可以用反射查找到,那说明
jacoco的代码库,确实被打到了apk里面
5.3 换AS版本
原来用的181,改成了最新的193,没有解决。
5.4. 换gradle版本
原来我们工程内置的是4.1版本,换成了其他版本,有的太高了根本无法构建,有的4.4 4.6的基本还是同样的问题
5.5 于是乎有了另外的思路,我去查它依赖的库
从task里,找到了dependencies这个任务,运行了下,
发现确实依赖的是0.7.4.201502262128
“`java
| +— project :message
| | +— org.jacoco:org.jacoco.agent:0.7.4.201502262128
| | +— project :user ()
| | — project :coreLibrary ()
| +— project :highlights
| | +— org.jacoco:org.jacoco.agent:0.7.4.201502262128
| | +— project :user ()
| | +— project :comment ()
| | — project :coreLibrary (*)
| +— project :search
| | +— org.jacoco:org.jacoco.agent:0.7.4.201502262128
“`
而且解析apk,发现确实apk里面集成的也是这个版本
如图所示:
这就更加确定了,这个版本,其实目前来说没有受我们当前代码的控制, 继续从build.gradle,找到其他依赖的库,挨个查看,主要找的是根目录下面的依赖,
在找了数十个依赖都无果之后,让我发现了这个库:
classpath 'com.android.tools.build:gradle:3.0.1
我们的代码当前引的是3.0.1版本,于是乎在Maven中央仓库中去找了下看看,地址如下:
https://mvnrepository.com/artifact/com.android.tools.build/gradle/3.0.1
它引用了一个编译期依赖: com.android.tools.build:gradle-core:3.0.1,地址如下:
https://mvnrepository.com/artifact/com.android.tools.build/gradle-core/3.0.1
如下图所示:
哇哦,发现了新大陆,它里面引用了jacoco的库,也确实是0.7.4.201502262128版本,后面的事情就没有任何选择滴开始了,升级这个版本!!!!
六、升级版本做了什么
可全局查找该属性所在位置
本例中,在项目工程下的build.gradle声明,修改后如下:
6.1 修改com.android.tools.build:gradle版本
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
...
}
6.2 原compile指令换成api
6.3 原testCompile指令换成testImplementation
6.4 原provided指令换成compileOnly
6.5 原instrumentTest换成androidTest
6.6 在gradle.properties文件增加两个属性
android.enableD8.desugaring=true
android.useDexArchive=true
当然,还可能有其他需要适配的地方,这就跟项目有关了,改到这个地方,我的问题已经基本解决了。
七、最后的分析
按照上述的解决问题分析,最终顺利生成了报告。
总结了下,其实在这个过程中,我们需要做的地方,最重要的也就是在debug版本里打开testCoverageEnabled这个开关,剩余的事情,其实根生成报告的方式有关了,如果按照api来生成报告,理论上来说,关于jacoco的其他地方,都不需要改动。
7.1 gradle的这个jacoco plugin
肯定是要apply这个插件的,这是前提
7.2 testCoverageEnabled
这个属性,在debug版本里,要设置为true,不然会无法生成
7.3 jacoco的toolVersion
这个版本号的设置,其实只影响用task来生成jacoco的报告,以及在编译期运行单元测试的时候生效,在你打完debug包进入apk之后,其实它就不生效了
7.4 关于对jacoco的配置
在build.gradle中对jacoco的配置,其实只是针对jacoco做了任务的扩展,可以让你改变jacoco插件对jacoco库引用的一些默认配置
比如生成报告的时候引用的jacoco agent 和jacoco ant
以及扩展一些其他生成报告的任务,可以修改class的文件夹属性和src属性以及exec文件的路径
(这里理解的比较浅,可能会有误解,还请大家仔细研究)
7.5 apk运行期间生成exec数据
划个重点:
因为我们是对apk的运行期做的覆盖率数据统计,所以主管这个jacoco数据生成的,其实控制在下面这个库里
classpath 'com.android.tools.build:gradle:3.2.1'
这么看来,社区的文章里面介绍时,如此的顺利,也恰好是因为他们用的这个构建版本刚好是支持0.7.5以上的jacoco core,这好像在3.1.4(获取还有再低点的,我没仔细看)以后就支持了,我选择的3.2.1内置的好像是0.7.9+的
八、最后贴一下我们用的覆盖率平台(其实不能算是平台,只能说是内部用的一个小工具,太丑了)
整体界面如下:
覆盖率统计列表界面:
覆盖率信息配置界面:
太丑了,不贴了,也因为这个平台踩了很多坑。。
有人会问,做这个平台的意义是什么,jacoco已经做的那么完善了?
前面也说过了,主要是可以节约其他测试人员的一些学习成本,毕竟不是每个人都愿意投入时间去学这么多杂的东西,另外也是个人对开发的爱好~~~
最后,我太啰嗦了,写了这么长,祝大家生活顺利、工作顺利~~~
,
* 注:本文来自网络投稿,不代表本站立场,如若侵犯版权,请及时知会删除