通用技术 jacoco 代码覆盖率使用中遇到的一些坑

Ramsey · November 19, 2018 · Last by 悠然 replied at May 20, 2020 · 7132 hits

今年5月离开某易来到新公司之后,一直希望把某易的代码覆盖率带过来,一方面可能之前在某易测试的时候习惯了提测后再检查代码覆盖率来进行补充测试的模式,另一方面新公司是家算不大的中小公司,在测试方面还是一直走比较传统的手工测试的路线,也希望可以改进一下,向精准化测试推进。
代码覆盖率(jacoco)的原理和具体搭建流程网上资料很多,我这里就不详细在陈述了,我这边搭建也是主要按照网上的流程通过jenkins+jacoco配置。起初在看到覆盖率数据正常获取,并验证了初步覆盖率结果的准确性后,便把它投入到项目中进行使用。但是在运行一段时间后,组织开发与测试同学一起进行覆盖率分析时还是发现实际数据与真实结果存在一定差异的问题。

首先基于jacoco工具本身应该不会存在覆盖率丢失和遗漏的情况,那么为了找到差异的原因,我这边对整个覆盖率统计的过程进行了复盘。
我们是通过jenkins+jacoco配置代码覆盖率,设置每天15点定时,通过jenkins任务去git上拉取服务代码,并对代码进行编译生成class文件。由于覆盖率统计和项目发布任务都在同一台jenkins机器上,所以编译的jdk是一致的,确保class文件不会有差异。然后通过ant build.xml文件去dump最新的exec文件,并通过merge保证覆盖率数据一直增量记录。最后通过jacoco插件实现覆盖率数据的正常展示。
表面上我当初搭建的时候觉得这样的安排很合理,但是当细细分析时,还是存在很多遗漏点:
1.覆盖率数据出现丢失:
我们jenkins覆盖率任务是每天17点定时去dump获取exec文件,但是我们知道jacocoagent由于注入在服务中,随服务的关闭而关闭,所以当服务发布重启时覆盖率数据会出现丢失,比如:服务A每天分别在9点和19点发布2次,那我们定时去dump exec文件是,明显会丢失从19点到9点那段时间的覆盖率,而且实际测试过程中由于项目偏敏捷模式,每天的发布频率并无法控制。而且实际除了发布重启之外也可能存在服务器挂掉的情况。
所以为了尽可能的全面的获取到覆盖率数据,我们需要提高dump exec的频率。在jenkins定时任务不变的情况下,通过编写python脚本来实现每15分钟去执行ant build.xml命令去dump最新的覆盖率数据。实际当然也可以把频率提升的更高。这样的方式,可以尽可能规避掉覆盖率数据丢失的情况。
2.覆盖率数据不准确:
大家可能会觉得奇怪怎么区分出覆盖率不准确和丢失的情况,其实是这样的,覆盖率丢失是之前操作了但是没有记录为已覆盖,但是再次操作会记录为已覆盖。覆盖率不准确则是不管怎么操作对应代码都不会记录为已覆盖。基于始终相信jacoco工具是不会有错的原则,所以我们可以把问题的焦点放在最容易出现不准的本地与服务端的class文件不一致的情况。
jenkins上的class文件是通过git上拉取的源码编译而成,由于前期规避了jdk版本不一致的问题,所以觉得本地的class文件肯定与服务端的保持一致。但是当切入到实际使用时还是存在遗漏。我们每次运行覆盖率任务时从git上拉取的都是最新的代码,所以编译的class文件也是基于最新的代码。但是实际dump过来的exec基于的未必是最新的代码,还是因为项目偏敏捷的模式,每天commit的代码量会比较大,为了保证项目正常测试不会频繁被打断,所以并不会实时commit新代码后立即去发布,这样就导致了你dump过来的exec是基于之前的版本的代码,从而本地和服务端的class是不一致的情况。
当我们认识到这个问题,其实解决也不难,从git上拉取代码,改为从jenkins上对应服务的发布任务中去拷贝源码和class文件,这样可以保证本地和服务端的始终都是一致的。

这些问题并不是很复杂,但是如果你只是按照教程去配置代码覆盖率还是会遇到的一些因为实际使用场景而无法预期的坑。所以记住配置说明文档也不是万能的。

共收到 18 条回复 时间 点赞

这两个点确实是实践中需要特别留意的。不准确的覆盖率很容易在前期失去用户的信任。

全量代码覆盖率? 针对于第一个问题15分钟job拉取还是会有覆盖率数据丢失的问题,建议在部署前主动拉取覆盖率数据,Jenkins + jacoco太不灵活了,建议把拉取覆盖率、生成报告全部封装成service服务对外提供接口,比如拉取覆盖率封装成http接口(做成异步的),这样就可以很好的嵌入CI流程里

可否把dump的任务放在服务部署的job步骤里面,build.xml里面设置为append追加模式
确保每次服务停止或重启时都会dump数据。

Ramsey #5 · May 09, 2019 作者
莱亚 回复

首先要你在重启或者服务停止前去dump数据,当然你的方案也是可以的。
但是有个风险就是,如果服务自己重启。如果你的服务比较稳定,我觉得也是可行的。
当然这个方案也是基于你每天有个定时任务去执行dump的基础上,毕竟你不可能指望要等到发布服务的时候才去dump覆盖率数据

从git上拉取代码,改为从jenkins上对应服务的发布任务中去拷贝源码和class文件,这样可以保证本地和服务端的始终都是一致的。----------------这句话没有理解,jenkins不是也是从git上去拉的代码么?

Ramsey #7 · July 24, 2019 作者

是的,但是比如你现在发布的1.1版本(因为不是每次开发代码提交,都会立即进行代码发布),你git上提交的已经是1.3版本,这时候你直接去git上拉,实际你会发现你拉取的代码和实际运行的代码是不一致的

关于第二点,我理解的是同样的代码分支,运行着的可能代码是老版本的,而git拉取的是新版本的,导致代码不一致;但是我有个疑问,就算当时的代码一致了,但是后续这个分支又有代码提交,导致之前dump的数据还是和最新的代码不一致,还是会导致覆盖率不准确吧?

对于第二个问题,我尝试了一下,发现,每次新提交代码之后,就算保持class文件一致,再去生成报告发现之前已经覆盖的语句还是变为没覆盖。。不知楼主有没有遇到类似的情况呢〜〜

Ramsey #10 · February 24, 2020 作者
hua 回复

你是每次拉覆盖率exec文件的时候,把之前的覆盖率文件删掉了吧。因为jacoco-agent是注入在服务中的,当你服务提交新代码发布重启后,注入的jacoco-agent也会重启,之前统计的覆盖率也会被清掉。所以你要每次拉取新的覆盖率后和之前覆盖率结果进行merge,做增量记录

Ramsey 回复

楼主你好,:
我操作步聚如下
1、第一次源码文件有一个方法,测试使其已覆盖,并拉取exec数据,生成的报告可以看到相关的代码已被覆盖。
2、编辑源码文件,新增一个方法,重新编译打包并启动这个项目,测试使新增代码覆盖,并拉取exec数据,但是这次生成的报告,只看到了新代码被覆盖,以前覆盖的代码(1中代码)没有被覆盖。
而且我reset是false,append是true,也就是exec文件是有以前的覆盖率数据的。后面我试了一下:
1、不改代码去重新启动项目,再生成报告,原覆盖率还在
2、不改代码去重新编译打包,生成报告,原覆盖率还在
但是一旦原代码有新增代码,并没有改变原来的代码,原覆盖率也会不存在了。
期待楼主的回复〜

12Floor has been deleted
Ramsey 回复

因为现在一个版本会有多次代码更新和修改,这样没办法保存所有的覆盖率数据,除非每次发版后都把用例全跑一次。。。
期待你的回复〜

Ramsey 回复

我应该找到答案了,新增代码之后重新编译的字节码文件跟之前不一样了,所以想要获取之前执行过的覆盖率数据,得把所有用例都执行一次。目前只能是这样了。
附上答案链接:https://groups.google.com/forum/?fromgroups=#!topic/jacoco/NneHas2oAdE
不知道你们在实际使用过程当中怎么规避这种情况发生,因为每次开发修改bug重新布署之后,之前的覆盖率就会丢失,就算那部份代码并没有被修改。

Ramsey #15 · February 25, 2020 作者
hua 回复

👍

Ramsey 回复

所以,这样来看,在敏捷开发中进行手工测试,是没办法比较好的收集覆盖率数据咯,更适用于自动化测试。对于手工测试时的覆盖率分析,楼主以前公司是怎么做到的呀,求指教〜

Ramsey #17 · February 26, 2020 作者
hua 回复

敏捷测试中,我们更多的是关注每个迭代的变更代码的覆盖率,也就是每次代码提交后,对这块改动代码是否覆盖,至于没有改动的代码,我们可以通过每次自动化去回归保证覆盖率。关于变更代码覆盖率你可以看下我的这篇文章:https://testerhome.com/topics/19077

Ramsey 回复

其实已经拜读过了,不知楼主是否方便加个qq或微信,我还有一点点疑问😁 ,打扰了😹

Ramsey #19 · February 26, 2020 作者
hua 回复

可以,微信:lifeng_176064119

悠然 · #20 · May 20, 2020
Author only
需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up