Java 代码覆盖率工具 JaCoCo:实践篇

reai1096 5年前
   <p>上周 <a href="http://www.open-open.com/lib/view/open1472174544246.html">JAVA代码覆盖率工具JaCoCo-原理篇 </a>简单介绍了JaCoCo其生成覆盖率的基本原理,这周的实践篇的主要内容就是将原理应用到实践中,本篇内容全部都是具体的项目使用实战经验,这里分享给大家,共勉~</p>    <p>一、覆盖率项目中使用介绍</p>    <p>本节开始详细介绍下项目中的JaCoCo实战经验。</p>    <p>下图是覆盖率在实际在项目中的主要实施点:</p>    <p><img src="https://simg.open-open.com/show/d6f9912eb1e90ac47564638dc1ce4f9b.png"></p>    <p>分别详细介绍下:</p>    <p><strong>1.1 确定插桩方式</strong></p>    <p>Android项目只能使用JaCoCo的离线插桩方式。</p>    <p>为什么?主要是因为Android覆盖率的特殊性:</p>    <p>一般运行在服务器java程序的插桩可以在加载class文件进行,运用java Agent的机制,可以理解成"实时插桩"。JaCoCo提供了自己的Agent,完成插桩的同时,还提供了丰富的dump输出机制,如File,Tcp Server,Tcp Client。覆盖率信息可以通过文件或是Tcp的形式输出。这样外部程序可很方便随时拿到被测程序的覆盖率。</p>    <p>但是Android系统破坏了JaCoCo这种便利性,原因有两个:</p>    <p>(1)Android虚拟机不同与服务器上的JVM,它所支持的字节码必须经过处理支持Android Dalvik等专用虚拟机,所以插桩必须在处理之前完成,即离线插桩模式。</p>    <p>(2)Android虚拟机没有配置JVM 配置项的机制,所以应用启动时没有机会直接配置dump输出方式。</p>    <p><strong>1.2 分析项目打包流程</strong></p>    <p>项目目前还是已build方式打包,属于Apache Ant方式。</p>    <p>插桩前先熟悉下项目build内容。</p>    <p>项目主要有几个build文件:</p>    <p>存放在根目录下的build.xml文件,这个是项目构建的组织文件</p>    <p>.ant目录下的build_common.xml,这个是构建时target内容。</p>    <p>.ant目录下的build_option.xml,定义的属性文件。</p>    <p>.ant目录下的build_plugins.xml,插件文件。</p>    <p>在插桩前,应该对项目构建过程做一个总体的熟悉,了解下每个target的作用,这样才能确定不会影响各个插桩点,不会遗漏,否则会在打包的过程中出现各种各样的问题。</p>    <p><strong>1.3 代码插桩</strong></p>    <p>http://eclemma.org/jacoco/trunk/doc/ant.html ,这个地址是JaCoCo的ant的说明文档。</p>    <p>里面简单介绍了其支持的task类型,包括:</p>    <p>Task coverage、Task agent、Task merge、Task report、Task instrument、Task dump</p>    <p>具体怎么使用可以参考里面的例子。</p>    <p>各Task实际调用的类,看一下JaCoCo的antlib.xml就知道了</p>    <p><img src="https://simg.open-open.com/show/eb8d7c018b7b4afad30f33c4f1bccf87.png"></p>    <p><img src="https://simg.open-open.com/show/cc5dd1ecafb52f1e65221ded3a120c8b.png"></p>    <p>项目根据自己的情况暂时只用到了Task instrument,其他dump、merge、report是通过其他方式使用的,具体后面有说明。</p>    <p>为什么没有用到dump、merge、report?</p>    <p>这种情况比较适合一个带有自动化测试的构建:打包、自动化测试、dump、merge、report。</p>    <p>项目部分功能需要手工测试,因此,上述几个步骤需要后面再另外处理。</p>    <p>OK,简单了解了JaCoCo的ant方式,下面开始对项目进行插桩打包。</p>    <p>项目的插桩修改步骤如下:</p>    <p>主要修改了build_common.xml和build_plugins.xml两个文件:</p>    <p>以下是build_common.xml的修改,build_plugins.xml的修改就不累述了,原理一样。</p>    <p>(1) 文件开头的命名空间加入</p>    <p>(2) 引入 JaCoCo 的 jar 和相关定义</p>    <p><img src="https://simg.open-open.com/show/15ecb411fbde2c2e99649c87a8c07dff.png"></p>    <p>(3) 重新定义 class 文件生成路径</p>    <p>(4) 修改compile编译节点,插桩注入</p>    <p><img src="https://simg.open-open.com/show/ec967668e9779a4f2e649ddd3166ce33.png"></p>    <p>(5) 修改打包package节点,主要是指定 JaCoCo 编译后的类路径</p>    <p>(6) 修改混淆obfuscate节点,增加混淆所需要的</p>    <p><img src="https://simg.open-open.com/show/8900a294da5935a48ecf23c5207d7010.png"></p>    <p>将delete、mkdir、unzip操作指向classes_instr</p>    <p>(7) 修改分包splitClasses节点,指向classes_instr</p>    <p>(8) 修改热补丁注入injectPatchCode节点,指向classes_instr</p>    <p>(9) 修改dex节点,指向classes_instr</p>    <p>(10) 修改dex_sub节点,指向classes_instr,同时在excludes中加入jacocoagent.jar</p>    <p><img src="https://simg.open-open.com/show/3a2d9f471f4c4e47a2ce1783da5bc337.png"></p>    <p>将上面的操作,做成全自动修改,打包成autoinsertxml.jar,放到打包服务器后台指定的目录下。</p>    <p>Jar包里详细内容如下:</p>    <p><img src="https://simg.open-open.com/show/bc3e880b526da3261f1376ced48f0440.png"></p>    <ul>     <li> <p>修改AndroidManifest.xml文件,增加一个覆盖率生成服务(这个后续的覆盖率生成工具用到)</p> </li>    </ul>    <ul>     <li> <p>修改build_common.xml文件,实现主干代码插桩修改</p> </li>    </ul>    <ul>     <li> <p>修改build_plugins.xml文件,实现插件代码的插桩修改</p> </li>    </ul>    <p><strong>1.4 打覆盖率包</strong></p>    <p>Jekin上已经配置好了jacoco_package任务</p>    <p><img src="https://simg.open-open.com/show/0f944f3336bd4bf3b4f2debf0a31b800.jpg"></p>    <p>按描述输入后,直接点击开始构建就行了,打包后的结果:</p>    <p>包括:未插桩的主干类文件、未插桩的插件类文件、三种方式的覆盖率包、mapping文件等等。</p>    <p>jacoco_package任务里面的具体内容做了什么?一起看看吧。</p>    <p>(1) 配置了参数化构建的内容</p>    <p><img src="https://simg.open-open.com/show/a59630394fe7b50c94ceac5703660002.png"></p>    <p>(2) 配置了构建描述</p>    <p><img src="https://simg.open-open.com/show/816e3eeb9c153632bcd391e52a04e12f.png"></p>    <p>(3) 配置了项目ID和创建精准入库任务</p>    <p><img src="https://simg.open-open.com/show/0b8ce65f645d64769b5a415efdb46b9d.png"></p>    <p>(4) Check out代码</p>    <p><img src="https://simg.open-open.com/show/dc80bed42524137c02d873e482af185b.png"></p>    <p>(5) 插桩</p>    <p><img src="https://simg.open-open.com/show/0c75c82cb7cf10f104cce15a21ccc085.png"></p>    <p>(6) 编译打包</p>    <p><img src="https://simg.open-open.com/show/64f3275c0c66af7915c1b77af6c54c20.png"></p>    <p>(7) 备份class</p>    <p><img src="https://simg.open-open.com/show/f53f6f1ff7385bfabda87b740e837039.png"></p>    <p>(8)保存存档文件</p>    <p><img src="https://simg.open-open.com/show/400143d7c51f5c481bd47e4306cb5916.png"></p>    <p><strong>1.5 执行测试,收集覆盖率结果文件</strong></p>    <p>覆盖率文件生成现在支持两种方式:</p>    <p>(1)覆盖率生成工具:一个专门用来生成覆盖率文件的APK。</p>    <p>(2)定时器的方式:在项目里新建一个定时器JOB任务,定时去收集生成覆盖率文件。</p>    <p>目前我们主要用第一种方式,下面都详细介绍下。</p>    <p>1.5.1 AndroidManifest文件的修改</p>    <p>增加了两个服务:</p>    <p>ResultManagerService:执行生成覆盖率数据。</p>    <p>ReSetManagerService:执行清理覆盖率数据。</p>    <p>1.5.2 生成覆盖率的apk工具和jacoco-cov-sdk.jar包</p>    <p><img src="https://simg.open-open.com/show/ba969dd289bde74a55706561ad2bf733.png"></p>    <p>工具总共有三个功能:</p>    <p>(1)生成ec文件</p>    <p>(2)启动定时器,按指定的时间生成ec文件</p>    <p>(3)清除覆盖率,会清除内存记录并且会删除sd卡存在的ec文件</p>    <p>工具原理:</p>    <p>(1) 生成ec文件</p>    <p>当触发这个操作的时候,其实会去启动项目中我们添加的ResultManagerService服务,它具体做的事情就是dump覆盖率数据,如下:</p>    <p>在ResultManagerService启动时调用jacoco-cov-sdk.jar包中的</p>    <p>ResultManager.dumpCoverageJacoco(true,filename)方法:</p>    <p>其主要功能就是反射调用jaCoCo的dump方法,来生成覆盖率数据,核心代码如下:</p>    <p><img src="https://simg.open-open.com/show/be568239958e286a6977578c55958f02.png"></p>    <p>(2) 启动定时器,按指定的时间生成ec文件</p>    <p>这个就是一个Timer,按指定的时间周期去dump覆盖率数据</p>    <p>(3) 清除覆盖率,会清除内存记录并且会删除sd卡存在的ec文件</p>    <p>当触发这个操作的时候,其实会去启动项目中我们添加的ReSetManagerService服务,它具体做的事情就是reset覆盖率数据,如下:</p>    <p>在ReSetManagerService启动时调用jacoco-cov-sdk.jar包中的</p>    <p>ResultManager.reSetCoverageJacoco()方法:</p>    <p>其主要功能就是反射调用jaCoCo的reset方法,来清理覆盖率数据,核心代码如下:</p>    <p><img src="https://simg.open-open.com/show/73d432247d692e1de9aca6dc692a3fb6.jpg"></p>    <p><strong>1.6 生成覆盖率报告</strong></p>    <p>通过编写report的build方式来生成报告结果。</p>    <p>这里写了一个生成报告的模版,使用者只需要copy到 本机上,按下面的说明修改、生成报告即可,下面详细介绍下这个模版的使用方法。</p>    <p> </p>    <p>1.6.1 模版目录介绍</p>    <p><img src="https://simg.open-open.com/show/f8a223c622db9c4a0ca24ede600f60be.png"></p>    <p>(1) libs存放几个jar包,分别为ant-contrib.jar、jacocoagent.jar、jacocoant.jar。</p>    <p>(2) result_xml目录会自动生成xml格式的报告。</p>    <p>(3) src目录是存放源码的,如果没这个,生成的覆盖率只有数据,看不到代码实际覆盖的内容。</p>    <p>(4) build文件,ant的执行内容为build_group或者是build_only。</p>    <p>说明:这个build文件需要根据实际的项目修改,修改一次以后基本不用变动。</p>    <p>(1) build_group文件,指定组生成的build文件,适合结果按组显示。</p>    <p>(2) build_only文件,没组的概念。</p>    <p>(3) build_property文件,存放的是ec列表文件名称。</p>    <p>build文件内容可以根据官方的demo参考。</p>    <p> </p>    <p>1.6.2 实际操作举例</p>    <p>1 、安装Apache Ant</p>    <p>网上下载或直接copy其他人机器上的就OK,设置下环境变量ANT_HOME和把bin目录放到path中,我用的是apache-ant-1.9.6-bin,有需要可以直接找我要。</p>    <p>2、取上面的模版目录放到你本机上(PC),有需要可以直接找我要。</p>    <p>3、生成报告,按以下步骤操作</p>    <p>比如拿到测试结果的ec文件有三个,分别是yyb1.ec、yyb2.ec、yyb3.ec</p>    <p>(1) 将覆盖率打包结果中的classes.zip丢到模版根目录中并解压。</p>    <p>(2) 根据打包时的svn地址和版本号,取下源码放如到src目录。</p>    <p>(3) 将ec文件(yyb1.ec、yyb2.ec、yyb3.ec)全部丢到模版根目录中</p>    <p>(4) 修改build_property文件,名称写如到value中(去掉ec后缀的)</p>    <p><img src="https://simg.open-open.com/show/cde34c24488de9a8dcc98209d1894f01.png"></p>    <p>(5) build文件,如无路径变化,基本不用修改</p>    <p>省电管理除了主干代码,还有插件部分,因此build文件取的build_group,分别为<group name="YYB">、<group name="plugin_power_save"></p>    <p>(6) 执行ant,report目录就会生成。</p>    <p>(7) report目录生成后,进去执行index就看到覆盖率报告。</p>    <p>省电管理的覆盖率生成结果:</p>    <p><img src="https://simg.open-open.com/show/f932bc9bd8b36a34846b5979d97139f0.png"></p>    <p>打开index后的结果,按build文件指定的分组生成了:</p>    <p><img src="https://simg.open-open.com/show/1a48d4d32076a9043fd958be74d19b00.png"></p>    <p>点击链接进入到实际代码中就可以实际覆盖的结果了。</p>    <p>这里有个注意的地方,如果想看到实际代码的覆盖率,编译的时候debug="true" 这个一定要设置。</p>    <p><strong>1.7 分析覆盖率结果</strong></p>    <p>网上关于JaCoCo覆盖率报告的分析有不少的文章可以学习。</p>    <p>这里阐明几个自己的观点:</p>    <p>根据项目的不同,在分析结果前先应该明确几个事情,包括</p>    <p>(1) 确定改动点的范围,根据这个范围才会有针对性的做分析。</p>    <p>(2) 改动点是否影响功能逻辑,如果不影响可以忽略。</p>    <p>(3) 改动点和其他功能是否存在耦合,如果存在,耦合的部分也要做分析。</p>    <p>我们主要从上面几点来分析覆盖率,查漏补缺,这些改动点大部分已经覆盖到了,基本认为应用的主要功能覆盖完全,当然也不是完全绝对,在测试过程中结合FreeTest、探索性测试等手段也是一种不错的选择,切记不要盲目的为了覆盖率而覆盖,覆盖率高不代表你真的覆盖完全了。</p>    <p>分析过程很多人觉得是比较痛苦的,不妨可以把这个过程当作是一种锻炼,前面的一切都只是一个铺垫,最最关键的在于分析阶段,一个出色的分析结果可以达到事半功倍的效果。</p>    <p>我们的方法是任务已tapd提单的方式创建,按照模版,附上需求链接地址、svn地址和改动范围、附件接受未插桩的class文件、测试后的ec文件等,分析出结果需要有分析过程、测试补充建议、分析耗时等等。</p>    <p>主要列出未覆盖场景、冗余方法、测试补充建议等等。</p>    <p>举个分析的例子:</p>    <p>需求: 消息盒子增量测试完成,进行覆盖率分析。</p>    <p> </p>    <p>1.7.1 熟悉需求用例</p>    <p>(1) 确认代码范围</p>    <p>根据需求,确定开发修改的代码范围</p>    <p>(2) 覆盖率报告分析</p>    <p>根据开发修改的代码范围,对覆盖率报告结果进行分析</p>    <p>(3) 确认未覆盖原因</p>    <p>找出未覆盖的部分,判断是否需要覆盖</p>    <p>(4) 输出测试策略</p>    <p>根据分析后的结果输出再次出测试策略</p>    <p>(5) 补充测试验证</p>    <p>根据策略补充相应的测试,再生成覆盖率,和前一次做对比,最终达到功能大部分覆盖。</p>    <p>二、覆盖率与BVT测试结合</p>    <p>通过两者的结合,可以得到每个BVT的用例的覆盖率数据,可以得出几个纬度的结果:</p>    <p>(1)用例和代码的对应关系</p>    <p>用例和代码的动态映射关系,可能会存在映射到的函数比较多,作者建议根据功能有针对的筛选出重点函数来做映射。</p>    <p>(2)上面映射关系汇总后,可以按方法的调用频繁度来优化我们的代码,优化调用频繁度高的代码,找出冗余代码等等。</p>    <p>下面介绍下整个过程:</p>    <p><strong>2.1 在BVT用例框架中插入覆盖率方法</strong></p>    <p>核心:找出关键点插入我们的覆盖率方法</p>    <p>(1) 在每个用例执行前,插入清理覆盖率数据的方法</p>    <p>在BVT基类的setUp()方法最后插入清理覆盖率数据的方法。</p>    <p><img src="https://simg.open-open.com/show/abd8545d897f5f396cf9922d81d0a6d0.png"></p>    <p>这样每个用例开始执行前,就会把以前遗留的覆盖率数据清除掉,保证每次覆盖率都是一条用例的执行结果。</p>    <p>(2) 在每个用例执行后,tearDown()方法中调用dump出覆盖率数据。</p>    <p>dump出来的数据用例执行过程中真实的覆盖率情况。</p>    <p><strong>2.2 执行BVT用例,得到覆盖率</strong></p>    <p>运行BVN的用例,用例执行成后输出覆盖率文件,一条用例对应一个覆盖率文件</p>    <p><strong>2.3 批量生成覆盖率报告,解析入库</strong></p>    <p>批量生成覆盖率报告,根据用例和报告对应关系做批量入库。</p>    <p><strong>2.4 分析覆盖率结果,得出用例和代码映射关系</strong></p>    <p>上面我们已经得出每一个BVT用例的覆盖率数据,对每一个覆盖率数据结果进行分析,得出几个纬度的数据,用例->包->类->方法的覆盖数据,这样每个用例和代码的映射关系就出来了。</p>    <p>然后根据用例对应功能的特点,再筛选出重点方法,形成一个比较精简的用例和代码映射关系出来,方便我们后续的改动点定位。</p>    <p>三、差异覆盖率和全量覆盖率</p>    <p>测试完后,根据覆盖率结果衡量测试覆盖程度,主要分为两种:</p>    <p>(1) 差异覆盖率:改动点的代码执行覆盖率情况</p>    <p>(2) 全量覆盖率:本次测试代码执行全部覆盖率情况</p>    <p>使用哪种覆盖率是由测试阶段的内容决定,比如上线前测试、集成或合流阶段,主要关注的是改动点的变化,使用差异覆盖率效果比较理想。如果是新增功能,使用全量覆盖率比较理想。</p>    <p><strong>3.1 差异覆盖率</strong></p>    <p>差异覆盖率主要是根据开发代码变更的diff差异,得出改动代码的范围,然后根据这个范围有针对性的只生成这部分改动的代码覆盖率结果。</p>    <p>通过覆盖率结果反向衡量测试的充分性,更好的和精准评估的测试范围去做比较。</p>    <p><strong>3.2 全量覆盖率</strong></p>    <p>全量覆盖率即全部代码的覆盖结果,不一定要全部去分析,只需关注改动部分及其耦合功能的覆盖情况即可,这里结合精准耦合分析结果一起分析。</p>    <p>四、衡量覆盖率结果</p>    <p>代码覆盖是一种状态指示器,而不是衡量性能或正确性的单元。</p>    <p>代码覆盖率是给程序员参考的,是给我们发现代码中问题的一种手段,可以发现过时的,未测试的类,还可以发现未经测试执行可能导致问题的路径。在实际项目中,代码覆盖率总是低于100%。取得完全覆盖是不可能的,如果取得,那也是非常罕见的。分析前一定要确定那些为必须覆盖,那些为可以或不覆盖,不要为了覆盖而覆盖,代码逻辑的熟练程度对分析覆盖率会有很大的帮助,一定要先梳理清楚。</p>    <p>五、本章小结</p>    <p>代码覆盖率是软件测试中的一种度量手段,主要用来描述程序中源代码被测试的比例和程度。</p>    <p>在单元和系统测试过程中,其常常被拿来作为衡量测试好坏的指标,甚至很多情况下用代码覆盖率来考核测试任务完成情况,经常会被要求代码覆盖率必须达到XX%以上,才算测试充分,于是乎测试人员或者开发人员费尽心思设计案例来覆盖代码,这种用代码覆盖率来衡量,有利也有弊。</p>    <p>给伙伴们的一些忠告:</p>    <p>(1) 覆盖率数据只能代表你测试过哪些代码,不能代表你测好这些代码。</p>    <p>(2) 不要过于相信覆盖率数据。</p>    <p>(3) 不要只拿语句/行覆盖来衡量。</p>    <p>(4) 路径覆盖率>判断覆盖>语句覆盖。</p>    <p>(5) 不要盲目的为了提供覆盖率而补充用例,应该想办法设计更好的用例,哪怕多设计的用例对覆盖率提升没有效果。</p>    <p>读者互动环节</p>    <p>覆盖率的结果是否可以作为评估测试充分性的唯一标准?或者说有没有其他手段一起来辅助评估,欢迎大家一起讨论。</p>    <p>精彩的回答除了可以移入精选留言,还有机会获取TMQ提供的精美礼品一份~ 期待您的回答哦~</p>    <p>长按指纹识别图中的二维码,获取更多测试干货分享!</p>    <p><img src="https://simg.open-open.com/show/0357c7362eb952c51e5db99d63e418ee.gif"></p>    <p><img src="https://simg.open-open.com/show/2aaf43cce7a6f79f2411874835659dbc.png"></p>    <p><img src="https://simg.open-open.com/show/378f4c7e65f2c9c0046b306b39ad43c2.gif"> 将我们公众号置顶 不会漏掉我们的原创干货哦!</p>    <p> </p>    <p>来自:http://mp.weixin.qq.com/s?__biz=MzIxNzEyMzIzOA==&mid=2652314603&idx=1&sn=e84c651a58f2fa9252c67d26b6454f38&scene=0</p>    <p> </p>