zoukankan      html  css  js  c++  java
  • 实战 | Java 服务端和 Android 端手工测试覆盖率统计的实现

    本文为霍格沃兹测试学院优秀学员关于后端和 App 端手工测试覆盖率的学习笔记。测试开发进阶学习,文末加群。

    一、前言

    代码测试覆盖率工具流行了这么多年,已经有很多成熟方案比如 Jacoco,我司近一段时间开始了这方面的摸索,很荣幸这个任务到了我的手里,于是乎就开始踩坑之旅。

    之前已经搞定了 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 启动 Ant

    这个就更简单了,因为这个是最原始也是直接的 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
    文件的配置,以及涉及到多模块的收集配置)

    四、App 端覆盖率进行时遇到的坑

    按照上述的这些精彩文章里,对 Android 端的代码覆盖率统计的介绍里,照理来说应当一气呵成了,但还是遇到了一些坑。

    比如: 在收集到手机端的覆盖率数据之后,传到后台,开始生成的时候,一直报以下错误:

    [ERROR] c.a.p.t.j.ReportGenerator - IO 异常 ,Cannot read execution data version 0x1006. This version of Jacoco uses execution data version 0x1007.  
      
    [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 啊。

    而且查了官方给出的文档,对应关系如下:

    GitHub 地址在:

    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 声明 , 修改后如下:

    1. 修改 com.android.tools.build:gradle 版本
    dependencies {  
            classpath 'com.android.tools.build:gradle:3.2.1'  
            ...  
        }  
    
    1. 原 compile 指令换成 API

    2. 原 testCompile 指令换成 testImplementation

    3. 原 provided 指令换成 compileOnly

    4. 原 instrumentTest 换成 androidTest

    5. 在 gradle.properties 文件增加两个属性

    android.enableD8.desugaring=true  
    android.useDexArchive=true  
    

    当然,还可能有其他需要适配的地方,这就跟项目有关了,改到这个地方,我的问题已经基本解决了。

    七、最后的分析

    按照上述的解决问题分析,最终顺利生成了报告。

    总结了下,其实在这个过程中,我们需要做的地方,最重要的也就是在 debug 版本里打开testCoverageEnabled
    这个开关,剩余的事情,其实根生成报告的方式有关了,如果按照 API 来生成报告,理论上来说,关于 Jacoco 的其他地方,都不需要改动。

    1. Gradle 的这个 Jacoco plugin

    肯定是要 apply 这个插件的,这是前提。

    1. testCoverageEnabled

    这个属性,在 debug 版本里,要设置为 true,不然会无法生成。

    1. Jacoco 的 toolVersion

    这个版本号的设置,其实只影响用 task 来生成 Jacoco 的报告,以及在编译期运行单元测试的时候生效,在你打完 debug 包进入 apk
    之后,其实它就不生效了。

    1. 关于对 Jacoco 的配置

    在 build.gradle 中对 Jacoco 的配置,其实只是针对 Jacoco 做了任务的扩展,可以让你改变 Jacoco 插件对 Jacoco
    库引用的一些默认配置。

    比如生成报告的时候引用的 Jacoco agent 和 Jacoco Ant ,以及扩展一些其他生成报告的任务,可以修改 class 的文件夹属性和 src
    属性以及 exec 文件的路径(这里理解的比较浅,可能会有误解,还请大家仔细研究)。

    1. APK 运行期间生成 exec 数据

    划个重点: 因为我们是对 API 的运行期做的覆盖率数据统计,所以主管这个 Jacoco 数据生成的,其实控制在下面这个库里

    classpath 'com.android.tools.build:gradle:3.2.1'  
    

    这么看来,之前参考资料里面介绍的如此顺利,也恰好是因为他们用的这个构建版本刚好是支持 0.7.5 以上的 Jacoco core,这好像在
    3.1.4(获取还有再低点的,我没仔细看) 以后就支持了,我选择的 3.2.1 内置的好像是 0.7.9+ 的。

    八、覆盖率平台

    其实不能算是平台,只能说是内部用的一个小工具,有点丑,整体界面如下:

    覆盖率统计列表界面:

    覆盖率信息配置界面:

    最后感谢在摸索的过程中,参考了很多有价值的文章,感谢指引,也欢迎大家一起探讨。

    References

    [1] Android 手工测试的代码覆盖率: https://testerhome.com/topics/2510
    [2] 浅谈代码覆盖率: https://testerhome.com/articles/16981
    [3] Android 手工测试代码覆盖率增强版: https://testerhome.com/topics/2524
    [4] Jacoco 统计 Android 代码覆盖率 [instrument 方式]:
    https://testerhome.com/topics/16376
    [5] Android Jacoco 代码覆盖率测试入门: https://testerhome.com/topics/17066
    [6] 定制触发条件| Jacoco 统计 Android 代码覆盖率:
    https://testerhome.com/articles/17546
    [7] AngryTester: https://github.com/AngryTester/Jacoco

    ** _

    来霍格沃兹测试开发学社,学习更多软件测试与测试开发的进阶技术,知识点涵盖web自动化测试 app自动化测试、接口自动化测试、测试框架、性能测试、安全测试、持续集成/持续交付/DevOps,测试左移、测试右移、精准测试、测试平台开发、测试管理等内容,课程技术涵盖bash、pytest、junit、selenium、appium、postman、requests、httprunner、jmeter、jenkins、docker、k8s、elk、sonarqube、jacoco、jvm-sandbox等相关技术,全面提升测试开发工程师的技术实力
    QQ交流群:484590337
    公众号 TestingStudio
    点击获取更多信息

  • 相关阅读:
    【代码笔记】Web-CSS-CSS伪类
    「MoreThanJava」Day 4:面向对象基础
    96年/离职8个月/拒绝华为offer/目前自由职业-记这大半年来的挣扎与迷茫
    nginx配置path_info,让codeigniter访问其它路由不是404
    Golang 与 JS 的字符串截取大同小异
    [Py] Python 的 shape、reshape 含义与用法
    微信公众号分享接口报错 "errMsg": "config:invalid url domain"
    vi以及vim打开文件中文乱码
    常见的移动端H5页面开发遇到的坑和解决办法
    CSS3 Transform 属性详解
  • 原文地址:https://www.cnblogs.com/hogwarts/p/15823120.html
Copyright © 2011-2022 走看看