一,项目简介
在移动端项目测试过程中,尤其是发版前的回归测试阶段,会遇到这样的情况,在测试过程中测试不断地发现问题,开发就进行修改,然后打包测试。而测试完成后呢,业务测试同学想知道整个回归测试阶段的覆盖率情况,但是针对每次不同的打包,会根据对应的版本生成相应的报告,不同版本的覆盖率执行如何进行合并呢?
在网上搜索了一下,jacoco本身有jacoco merge功能,但是只针对同一版本的,如果版本变化了,也就意味着类有变化,那这个类在老版本中所有的覆盖率数据就会被丢掉。这样一来,必须会丢失一些版本中的覆盖率数据,同时,如果函数有变化,则这个函数调用链路上的所有功能就要重新测试,原来的覆盖率数据就要丢弃。综合考虑,无法直接使用jacoco merge来进行跨版本的覆盖率数据合并,这就要求我们自己开发一套覆盖率合并方案。
二,覆盖率合并方案介绍
1,网上方案调研
通过调研网上现有的jacoco覆盖率合并方案,得到了一些思路,但是没有切实可行的方案。参考文档:
-
基于 Jacoco 的二次开发【解决不同版本 exec 数据合并问题】:
https://testerhome.com/topics/30776
-
jacoco的多次代码提交merge分析:
https://chowdera.com/2022/195/202207130538134324.html
-
写给android同学的代码覆盖率讲解:
https://www.jianshu.com/p/97caec282998
-
JaCoCo探针策略原理及案例总结:
https://bbs.huaweicloud.com/blogs/274224
2,自研合并方案
针对业务同学的要求,需要对同一分支不同版本的多次打包产生的覆盖率数据进行合并,不同分支的合并起来意义不大。但合并过程中也涉及到众多处理逻辑,现在将整体方案流程介绍如下:
介绍:
1,对比要合并的版本,先按版本合并覆盖率数据文件,找出diff的函数列表;
2,通过diff函数列表,查询调用链路,找到其影响的上游函数列表,组成受影响的函数列表。
3,对受影响的函数列表,找到其在老版本中的行号,去掉对应的行号的覆盖率信息。
4,根据diff函数列表,找到其所在类中的位置没有变化的函数列表,以及位置变化的函数列表。
5,针对位置没有变化的函数列表,将旧版本中的覆盖率信息,写入到合并后的覆盖率文件中;
6,针对位置有变化的函数列表,将旧版本中的覆盖行号校正为新版本中类的对应的行号,写回覆盖率文件中。
7,循环处理所有需要合并的版本覆盖率数据文件,生成最终的覆盖率文件。
三,Android 跨版本覆盖率合并
针对Android系统的跨版本覆盖率合并,diff函数可以通过git提供的功能来实现,查询类中函数的起始位置,由主站的同学提供的解析kotlin和java文件的功能来解析。而最重要的就是,对覆盖率数据文件EC的处理。主站同学提供的jacoco-parser.jar工具,用来解析覆盖率ec文件覆盖的行号,而无法修改EC文件,而我们的需要对EC文件进行处理。
1,EC文件的处理
这样的处理没有办法直接通过java代码或是python来处理,只能开发一个单独的工具,来操作EC文件。所以就优化了主站同学的jacoco-parser.jar,引用jacoco插件来处理EC文件,最终生成的工具是jacoco-parser2.0.jar.
2,版本合并时函数处理
合并版本的时候,需要处理的函数信息有如下几类:
-
diff函数及其在调用链路中影响到的函数列表;
-
diff函数所在类,位置没有变化的函数列表;
-
diff函数所在类,位置有变化,需要在新版本中修正行号的函数列表。
具体的处理流程如下:
为了与先前的覆盖率生成功能保持一致,节省相关操作,故将此功能放到了Android Agent上,以服务的形式对精准测试平台提供服务。
3,精准测试平台合并覆盖率
为了让测试同学来合并覆盖率操作,需要在精准测试平台上添加合并覆盖率的操作,通过精准测试平台向Agent发送消息,同时借助于原来的生成覆盖率报告的逻辑生成最终的合并后的覆盖率报告。最终的流程如下所示:
当得到了合并后的最终all-merged.ec文件后,就可以根据项目的其他信息,如当前分支,对比分支,最新的构建号等生成合并后的全量及增量覆盖率报告。
四,iOS 跨版本覆盖率合并
iOS端的覆盖率合并,相对于Android端容易一些,因为iOS生成报告之前,会生成一个info文件,处理这个文件比EC文件简单多了。整体合并方案就是对比两个版本的info文件,根据diff函数列表,位置变化的函数列表进行处理。
1,iOS跨版本合并流程
通过上面的处理,以及对比合并后的覆盖率报告,发现一个问题:合并后的覆盖率报告行覆盖率没有问题,但是方法覆盖率不正确,也是不能直接对数据进行处理的。需要一定的逻辑处理才行,经过调研后,可以从下面的方式来进行。
2,iOS Swift跨版本覆盖率合并方法合并
目前的功能暂时不对覆盖率合并后的文件进行校正的,根据业务同学在使用过程中,是否需要这个指标,如果需要,在Agent上添加相应的功能即可。
其中的合并两个info文件的操作,是自研的代码来处理的,主要思路是:
-
先以最新的版本的info文件为基础,分别使用sed获取其中的文件以覆盖率信息;
-
再从旧的版本的info文件中,找到相应的文件的覆盖率信息;
-
按不同的情况,如新文件中有,旧的文件中没有;旧的文件中有,新的文件中没有;两个文件中都有,进行合并行覆盖率数据;
-
最后将合并后的info文件写入新文件,再拷贝成较大的版本的文件,循环合并所有版本;
这种方法没有错误,但是当info文件中的文件信息过多时,如60多万行数据时,速度就非常慢,差不多要一个多小时才行。
有什么办法可以优化一下吗?
1,通过在网上搜索相关资料,找到lcov的使用文档:https://blog.csdn.net/qq_36614557/article/details/120264262
文档中说可以使用-a参数 合并两个lcov生成的文件,如下所示:
2,通过两次详细搜索,合并方法如下
lcov -a test1.info -a test2.info -o allreport.info
经过验证,合并结果达到了预期,而合并速度快的不是一个数据级,非常开心!
3,在生成iOS覆盖率报告是,生成的info文件命令
xcrun llvm-cov export "+procpath+"Kima --instr-profile="+procpath+"coverage_merged.profdata -use-color --format=lcov > "+procpath+"allreport_"+commitid+".info"
完全符合要求。
于是合并info文件的函数变成如下所示,比原来的简单了太多了:
def newMergeTwoInfoFile(self,srcinfofile,destinfofile):
"""
使用llcov -a 合并(归并)多个lcov生成的info文件
:param srcinfofile:
:param destinfofile:
:return:
"""
newinfofile=destinfofile[0:destinfofile.rindex("/")+1]+"merged_report.info"
if os.path.exists(srcinfofile) and os.path.exists(destinfofile):
mergecmd=self.lcovcmd+" -a "+srcinfofile+" -a "+destinfofile+" -o "+newinfofile
os.system(mergecmd)
#将删除过覆盖率数据的info文件,更名为老的文件
rmcmd="rm -rf "+destinfofile
os.system(rmcmd)
mvcmd="mv "+newinfofile+" "+destinfofile
os.system(mvcmd)
else:
print("要合并的两个info文件:"+srcinfofile+","+destinfofile+"不存在!")
最终的合并覆盖率报告执行时间,也由原来的一个小时二十多分钟,降低到了二十多分钟!
3,精准测试平台iOS覆盖率合并方案
由于在容器上无法处理iOS覆盖率报告,所以只能根据keep构建号把覆盖率数据文件profraw上传到Agent上来处理。iOS Agent需要做大量对覆盖率数据处理,生成info文件,合并info文件的操作。其中对函数的过滤和处理使用的是经过二次开发的工具drafter,这个工具可以根据文件来返回文件中的函数列表,包括起始位置。
五,总结
覆盖率跨版本合并,正常情况下是不允许的,这也是为什么jacoco就不支持的原因。因为类一旦变化了,类中函数的位置,函数内容都会变化,同时其调用函数也会受到影响。极端的情况,就是函数中的逻辑变化了,先前与这个函数相关的所有操作都要重新测试。
1,Android中的覆盖率数据是通过探针来控制覆盖情况的,如果多个探针同时控制一个覆盖逻辑的话,上面的覆盖率行号的修改就会出错;由于探针和行号不是一一对应的,所以很难根据探针准确地修改行号的覆盖情况。现在的合并方案虽然比直接jacoco merge更加准确一些,但是也不完全准确。
2,iOS覆盖率的合并,其实是对Info文件做了处理,针对覆盖的行号的后面的数字做了处理,如原行号覆盖为0,改成1还好理解。如果原来行号覆盖数据是2,要合并的是3,合并后改成了5。这个5在报告中如何渲染的呢?其实没有太搞明白。现在info文件信息解读,网上也不多。
六,常见问题
在实际使用过程中,增加了对项目Module模块的覆盖率的收集,生成的测试报告显然比原来的更加全面了,但出现了两个问题:
1,覆盖率收集的Pod相关的文件,生成报告的时候找不到文件?
由于项目和Module的调用,会出现一些Pod中的头文件信息,这是因为主代码与pod module交互的接口或是过度等。但是生成覆盖率报告的时候,在源项目中是找不到对应的文件的,所以genhtml报告时会因文件找不到出错。
解决方案:在生成覆盖率报告info文件后,过滤掉info文件中与pod相关的文件及其覆盖率信息,以保证生成html报告时不会出错文件找不到的问题。
2,iOS覆盖率合并的时候,合并版本与项目分支对应的版本不同,会出现文件找不到?
在进行覆盖率的合并的时候,由于创建覆盖率任务与生成报告时间间隔内,如果开发有提交代码,早期的更新项目的逻辑是拉出对应分支最新 的版本,可能造成新版本修改了文件,老版本的覆盖率报告找不到文件。
解决方案:在生成合并覆盖率合并报告时,先获取要合并覆盖率报告任务的最新版本号,将项目切到这个版本再生成报告。生成完成后,再切回分支的最新版本,防止影响其他的操作。