代码覆盖率在手工测试上的实践(转自网易数帆)

1 为什么要做手工测试覆盖率

相信测试人员对代码覆盖率检查都具有一定程度的了解。简而言之,代码覆盖率统计用于测试实践指的是在测试过程中收集数据并使用一定的规则与源码相映射来得出结论的过程,可用于测试指导。覆盖率是度量测试完整性的一个手段,是测试有效性的一个度量。一般的测试覆盖率使用场景主要是用于自动化测试:测试用例的自动化脚本跑完后就可以使用覆盖率生成工具统计,这个过程仅仅是少则几秒多则几分钟的事。覆盖率报告从代码层面客观地反映出测试用例的完备程度,并且能指导测试人员思考测试用例的设计思路,因此能一定程度上提升发布质量信心。

然而,在项目新功能刚开发完成且尚处于前期手工功能验证的阶段,当完成手工测试后,心中有时不免信心不足,隐隐担心测试用例设计是否已经足够充分,有没有遗漏掉什么缺陷。此时,自动化测试代码覆盖率给了我们一丝启发:我们是否可以将代码覆盖率运用在手工测试上,从一定维度上提升我们对产品发布质量的信心?

2 工具和使用

覆盖率统计的工具有很多,如Jacoco、Clover 和 Cobertura。相对而言,作为开源的Java 插件,Jacoco更贴近我们的需求,不仅因为它使用了 Eclipse Public License,方便个人用户和商业用户使用,而且能较好得集成进Sonar、Jenkins等平台。

2.1 原理

来看一下Jacoco的原理。Jacoco有两大模块,Jacocoagent和Jacocoant。Jacocoagent作为服务,Jacocoant作为客户端。测试服务器上开启tcp端口,Jacocoagent作为服务跟随项目进程启动并监听测试过程、记录测试覆盖的数据,同时监听客户端Jacocoant通过Socket发送的请求,诸如生成测试覆盖率文件、映射源码得出测试报告等。

2.2 build.xml

Jacocoant是通过Jacoco的配置文件build.xml来发起请求的。通过分析配置文件可以帮助我们更好地理解这个过程:

图1 Jacoco的build.xml配置文件

此处是配置远程监听的服务器和端口以及用于映射的源码路径和测试对应的class文件路径。

图2 配置服务器、端口、源码路径及对应的class路径

此处是生成覆盖率二进制文件的相关配置。当测试完成后只需要执行命令ant dump就可以在指定路径生成指定名称的exec文件。用户在此处执行命令ant dump,实际上是发送了Socket请求,Jacocoagent在服务器上根据之前监听并记录的测试数据生成后缀为.exec的二进制覆盖率文件。

图3 生成覆盖率二进制文件的配置

此处是生成覆盖率报表,用于可视化展示。主要是把上一步产生的exec文件通过一定的映射算法与对应的源码进行比对,并且进行图形绘制,具体的结果表现为如图4格式的文件(红色表示未覆盖,绿色表示完全覆盖,黄色表示部分覆盖):

图4 生成的覆盖率报表

一般我们选择html形式,事实上Jacoco支持cvs、xml等其他形式的报表,从以下的agent源码可以看出:

图5 Jacocoagent源码

2.3 使用

build.xml有一定了解后,我们可以了解到,Jacoco的一个使用过程基本如下:

  1. 配置文件中定义好测试服或者线上服的ip、端口、源码路径、class文件所在路径(如果需要将覆盖率的执行结果展示在Jenkins上则也需要配置Jenkins路径),并且在相应的target中定义需要生成的exec文件;
  2. 将项目的覆盖率源码路径下的源码更新到需要测试的版本;
  3. 编译发布,使服务器中的class文件保持最新,与源码一一对应;
  4. 跑动自动化测试用例;
  5. 跑完用例后执行ant dump生成exec文件;
  6. 执行ant report,生成覆盖率报告;

这样,本次自动化测试的代码覆盖率统计就完成了,可以看到如下的报告文件:

图6 覆盖率统计报告文件

当然以上5,6两步以及其他一些诸如先清空之前覆盖率文件再生成等个性化操作都可以自己定制,并定义在一个target中,如图7所示:

  

图7 target定制个性化操作

3 手工测试覆盖率的难点

以上介绍的是一些最普通的覆盖率统计过程,通过查看Jacoco的源码,我们可以发现很多个性化的设置,帮助我们实现各种实际测试过程中的特殊需求,比如手工覆盖率。由于Jacoco最初出现是针对单元测试,因此我们自然而然将其运用到自动化测试覆盖率统计中。手工测试覆盖率的计算虽说与自动化原理基本一致,从表面来看仅仅是一个持续时间长短的问题。但也正因为手工测试一般持续的时间较长,反而会引发其他问题。

比如测试环境多项目占用的情况下,自动化测试时间较短,一般选择一个特定的时间不会受到其他代码分支的影响。但是手工测试过程中就有可能遇到需要切换代码分支的情况。由于Jacoco服务本身依赖当前项目服务,因此当项目切换到其他分支并重启服务器时,Jacoco服务会同时挂掉。另外,又由于Jacocoagent每次监听测试过程采集的数据放在内存中,重启后数据将会被清除。因此之前介绍的用法已经不能满足我们的测试需求。 同理,一个版本的测试必然发现bug,必然出现代码的更新和重部署,虽然不需要切分支,但也需要重启服务,问题依然存在。 如何解决这些问题,让手工测试也能得到覆盖率数据的支持成为手工测试覆盖率的难点。来分析一下Jacocoant的源码,通过Jacocoant.Jar反编译可以看到,Jacocoant支持的基础targetantlib.xml文件中说明了,主要有以下一些:  

图8 Jacocoant支持的基础 target

此处的merge就是为以上难题提供的一个最佳解决方案。由于每次dump时Jacocoagent都会生成一个xxxxx.exec的覆盖率文件,虽然重启服务会导致内存中收集的覆盖数据被清除,但这个覆盖可以被保存下来。所以我们可以在每次需要重启服务时生成一个exec文件,并进行合理的命令,当测试完毕之后进行merge操作,配置如下:

图9 merge_exec具体配置

最后生成的xxx-merged.exec就是我们的目标二进制文件。然后再执行命令ant report,就可以得到目标覆盖率报告。
以上方法解决了服务重启数据的保存和二进制覆盖率结果的增量生成,对于多个分支Jacoco会在每个分支都生成exec,如何分离出目标分支的exec文件并merge起来就是仅存的问题了。其实这个简单,只要在每个分支代码切换前将build.xml文件更新就可以,可以根据代码分支来命名不同的分支对应的exec并merge起来,主要修改以下位置:

图10 不同分支的exec的merge

图11的执行log可以看出merge过程就是把每个阶段已经生成的sdk分支相关的exec全部整合成一个sdk-merge.exec。   

图11 执行的log文件

4 改进点和遇到的一些坑  

实践过程中也发现有一些坑,踩中了耗费不少时间,主要有以下两点:
1)注意保证源码和class文件的一致性。由于测试服上一般是不存源码的,源码在编译机上编译打包后远程拷贝到测试服,因此覆盖率统计时需要手动拷贝一份源码到服务器上。这种情况下,会因为疏漏发布了最新的版本但忘记更新那份孤立的源码,这样就会导致Jacoco将源码和exec进行映射时出现行错乱,具体表现如图12

图12 Jacoco将源码和 exec进行映射时出现行错乱

从图中可以看出源码的注释也显示为被覆盖,反而诡异的[地]出现方法头未被统计到而内部被统计的情况。出现这种情况基本可以断定为源码和class文件的不对应。更新后再重新跑就可以了:   

图13 更新一致性后行错乱问题解决

2)Jacocoagent在服务器上开启服务时影响主系统业务导致编译不通过。我们在服务器上开tcp端口开启Jacocoagent服务时是把Jacocoagent作为Jvm的参数传入的,如下:

图14 将Jacocoagent作为Jvm的参数传入

当不传入-Xverify:none这个参数时,由于Jacocoagent是通过注入到代码中去监听和统计的,所以主系统会检查发现有异常注入,从而导致编译失败,主业务挂掉。因此必须注意加入此参数。   

踩过坑之后,当然对目前的实践也存在着一些想法和改进思路,期望可以在接下来有所实践,总结主要有以下两点:

1)对于手工测试覆盖率统计时有多个代码分支切换的情况,目前采用的是A分支中断重启服务前手动生成exec文件并且切换build.xml文件来做B分支的覆盖率,最后分别merge不同版本的覆盖率情况。这个过程没问题,但手工执行未免繁琐。接下来可以使用shell脚本完成这一个系列的操作,在一定程度上提升效率。
2 )将覆盖率统计迁移到线上环境做尝试。只要修改配置文件中服务器ip和端口。  

5 从覆盖率统计看测试设计

5.1覆盖率指导测试设计

覆盖率报告直观地给出了测试用例对代码块的覆盖情况,下图是测试完成后生成的最初那份报告:  

图15 覆盖率报告

由上图15可以看出,覆盖率达到了76%,这个结果只能说尚有改进空间。查看具体的覆盖情况,可以看到尚有较大一部分分支没有覆盖到,举例如下:

图16 覆盖率只有 76%

我们可以看到业务上的这两种异常确实有用例未覆盖到,重新设计用例并执行后,该部分代码块的情况如下:  

 

图17 重新设计用例之后的代码块覆盖情况

   
覆盖率也提升到了90%:  

图18 重新设计用例之后覆盖率上升到了 90%

以上的例子看出,覆盖率报告让测试人员知其然并知其所以然,在一定程度上是可以指导测试人员补充测试用例,帮助覆盖某些重要的分支。需要注意的是,这种方式并不能取代探索性测试。从另外一个角度来看,测试人员通过对报告的几次分析可以认识到测试设计时的缺陷并调整未来的工作策略。

5.2 覆盖率不等于测试完备性

覆盖率确实可以在测试的广度上作为测试设计的一个补充,我们可以尽量覆盖到代码的各个分支,精确到每一行。但是,在测试的深度上并不能体现他[它]的作用,举个例子:

图19 覆盖率不等于完备性的例子

上面图19展示的上半部分图是该版本测试主要功能点的源码,下半部分图中是依赖的一个工具类中的方法。可以看到这条分支已经跑到了,但从下图中的方法中可以看出存在两种情况返回到以上分支。测试时仅测到一种情况,但从覆盖率统计来看确实完整覆盖了。可见从测试的深度来讲,覆盖率并不能与测试完备性划等号。   
或许有人会说,应该把依赖的工具类也已经放进来做覆盖统计。但实际情况是,工具类多半是个历史积累的产物,很多情况下当前版本仅仅是用到里里面的小部分代码,因此统计进来就不具备参考意义。遗憾的是Jacoco仅支持类级别而不提供方法级别的覆盖率统计。

5.3 无法覆盖到的*%

先来看下面一张图:

图20 无法覆盖到的*%

这种情况,从业务的角度完全无法模拟,因为正常用户出现这种情况之后请求根本无法到达服务器,唯一存在这种情况的就是安全攻击了。像这样的代码块是服务器为了自身的防御做的一些容错,用例无法覆盖。反映到覆盖率报告上就是覆盖率报告总是有那无法覆盖的*%。然而,对于这种情况,我们目前也在摸索实践中。
在实践的过程中和吕泽廷同学对相关实践也进行了一些讨论,并得到一些启发,再次表示感谢。

本文来自网易实践者社区,经作者何美玲授权发布。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值