cover_image

PHP代码覆盖率工具探索&应用

韩蓓蓓 北京顺丰同城科技技术团队
2025年02月06日 12:44
一、背景   
在平时的工作中你有没有遇到过这样的问题:
  1. 你正在专心工作,突然收到了企业微信报警,值班人说道:“小贺快看报警你们的模块fatal了。”小贺一看报错代码:“这个需求是rd自测的,这错误还挺明显的,rd肯定都没好好自测就上线了,我赶紧找rd解决,看来得想办法评估D级项目的自测覆盖率啊”
  2. 在项目站会上,rd小张说:“这个需求我开发完了,可以提测了,我一会儿发提测邮件“。qa小刘说:“你好好自测了吗,别就走个主流程就提测留好多bug给我。”rd小张(心虚的)说:“必须自测了,肯定没问题,你测吧”!结果qa测试时发现,虽然主流程没问题,但还是这么多bug,肯定没好好自测,是时候监控一下开发提测时的自测粒度和质量了。
  3. 你们组来了个新的QA小王,你交给他一个需求测试,到了上线的日期你问小王:“小王你这个需求测完了吗?能上线了吗?”小王说“测完了,可以上线了”。负责任的你提醒道:“A接口测试了吗?B脚本看了吗?C消息没有漏掉吧?D场景考虑到了吗?balabala“。这时候你想:如果有个工具能看到QA设计的测试用例执行之后是否实现了增量代码全覆盖就好了,那上线我就更放心了。    
  4. 你写了个接口的自动化,领导问:“你这个接口自动化覆盖是多少?” 你说:“嗯。。可能,大概,也许能覆盖到这个接口的80%功能吧”。领导:“80%?你怎么评估的80%,剩下的20%是哪些代码和场景呢,你这些自动化case是否能够覆盖到所有的重要代码分支进而实现自动化回归测试呢?”
其实上面的问题都有一个共同的特点,就是你这行代码,这个场景到底有没有测到,既然人为评估主观性太大,那我们就用数据说话,实时去监控你的测试过程,记录你的测试结果,这时你想到这不正是代码覆盖率工具的功能吗,有了这个工具,再也不用担心被RD忽悠了。


二、代码覆盖率介绍   

代码覆盖是软件测试中的一种度量手段,是一种白盒测试方法,指我们运行所有项目中的测试方法后,被执行的语句与所有语句的比值,也就是代码在测试中被覆盖的程度。

从覆盖源代码语句的详尽程度分析,主要包括3种不同的覆盖标准,分别是:
  • 行覆盖,也叫做:语句覆盖、段覆盖或基本块覆盖
这是最常用也是最常见的一种覆盖方式,就是度量被测代码中每个可执行语句是否被执行到了,行覆盖是很弱的逻辑覆盖,它只管覆盖代码中的执行语句,却不考虑各种分支的组合等等。
以下一条路径case可达到行覆盖率100%。    
图片
  • 分支覆盖,也叫做:条件覆盖、判定覆盖或边界覆盖
它是比行覆盖稍强的覆盖标准,它度量程序中每一个判定的分支是否都被测试到了,设计足够的测试用例,使得程序中的每个判定至少都获得一次“真值”或“假值”,或者说使得程序中的每一个取“真”分支和取“假”分支至少经历一次。
以下2条路径的case即可达到分支覆盖率100%
图片
  • 路径覆盖,也叫做:断言覆盖
它度量了是否函数的每一个路径分支都被执行了。就是所有可能的分支都执行一遍,有多个分支嵌套时,需要对多个分支进行排列组合,可想而知,测试路径随着分支的数量指数级别增加。
以下4条路径的case可达到路径覆盖率100%    
图片    
因此在代码覆盖程度上路径覆盖 > 分支覆盖 > 行覆盖
了解了代码覆盖率的基本概念后,那业界有哪些工具可以统计到代码覆盖率来解决我们上面的问题呢?通过查阅文档了解到java覆盖率统计工具有jacoco,go语言有gcov工具,PHP语言有php-code-coverage,由于我们智域项目目前的主要开发语言是PHP,所以主要研究了php-code-coverage工具。关于这个工具的介绍以及在实际项目中的应用网上可供参考的资料并不多,不像jacoco那样有成熟的社区以及实际应用的案例可供学习和参考,所以大部分需要自己去探索。


三、PHP代码覆盖率工具介绍  

1、php-code-coverage工具介绍    

php-code-coverage是一个开源类库,它使用了PHP的Xdebug扩展所提供的代码覆盖率功能,可以作为组件集成在phpunit中,提供了收集、处理和展现PHP代码覆盖率信息的功能,它会动态跟踪php代码的执行,合并并生成整个php项目的代码覆盖率,最终以html、xml或者text等形式展现,同时也能生成Clover、Crap4J、PHPUnit等形式的基于XML的代码覆盖率信息记录文件。
以此为依据,我们可以分析全量php代码覆盖率,也可以分析新增php代码的增量覆盖率,评估测试用例的质量,决定是否要增加某些测试用例以提高代码覆盖率。
php-code-coverage工具可以实现以下衡量方法:    
  • 行覆盖率(Line Coverage):按单个可执行行是否已执行到进行计量;
  • 函数与方法覆盖率(Function and Method Coverage):按单个函数或方法是否已调用到进行计量,仅当函数或方法的所有可执行行全部已覆盖时 PHP_CodeCoverage 才将其视为已覆盖。
  • 类与特质覆盖率(Class and Trait Coverage):按单个类或特质的所有方法是否全部已覆盖进行计量;
php-code-coverage实现覆盖率的统计和报告生成主要有三步:
  • 代码覆盖信息收集
此功能由xdebug组件实现,实现原理:
图片
如果想要收集某个PHP文件的覆盖率,只需要在被测文件的开头调用工具的start()函数,然后执行测试代码,结尾调用stop()函数即可:    
图片
  • 信息存储
由php-code-coverage组件实现,将xdebug收集到的信息记录下来生成cov文件
  • 生成覆盖率报告
由php-code-coverage组件实现,使用phpcov提供的命令生成html、xml或者text等形式的报告,以便查看覆盖结果
第二步中我们提到如果想要统计一个文件的覆盖率需要入侵业务代码调用工具的start()和stop()函数,但是实际项目中我们的被测文件成千上万,不可能对每个文件进行修改,那怎么配置才能在不修改业务代码的前提下引入覆盖率工具呢?stop()可以通过register_shutdown_function(‘__coverage_stop’)来解决, 所以问题就在于如何在真正代码执行之前执行start()?

2、项目配置  

想要在php代码运行之前调用start函数开启代码覆盖率,针对PIE框架我们想到4种配置方式:
  1. 在ini中引入prepend文件:auto_prepend_file = /***/prepend.php
  2. 在每个模块的文件入口文件中引入prepend文件:一般为php文件(比如:/home/sftcwl/odp_sds/webroot/srm/index.php)    
  3. 在nginx.conf中引入prepend文件 --- 对于该域名的请求会加载该文件(配置后重启nginx)
    图片
  4. 添加引用文件到入口文件 php/phplib/sf/Init.php中图片
由于第一种配置方式配置后需要重启php,比较繁琐;第二种和第三种方式无法统计脚本的覆盖率,因此实际应用中我们使用第4种方式,这种方式配置方便且被测接口和脚本都能统计到覆盖率。进行以上配置之后,我们成功生成了一份覆盖率报告,但是php-code-coverage工具目前只支持全量代码覆盖率的统计,在实际项目中我们关心的是本次迭代修改的代码有没有测试到,覆盖率是多少,所以全量覆盖率报告会有冗余,影响我们对报告的分析和查看。为了更精准衡量测试范围和评估影响面,我们需要对工具做一些改造,生成增量代码覆盖率报告。

3、增量代码覆盖率支持  

为了支持增量代码覆盖率我们需要完成以下功能的开发:
  • 获取各个提测模块分支与master的diff文件,因为要统计行覆盖率,所以文件需要精确到行。
  • 改进覆盖率信息处理文件(包含start()和stop()功能的php文件)将diff文件作为统计覆盖率的白名单文件,取代全量项目文件作为白名单文件,来为生成的覆盖率文件瘦身,提高性能。
  • 过滤项目中不必要的接口(比如轮训和自动刷新接口),减少覆盖率文件干扰。    
  • 开发新的生成报告的命令,支持基于第一步生成的diff文件生成增量代码覆盖率。
通过增量代码覆盖率报告我们可以看到每次测试的代码覆盖率以及明确哪些新开发的代码我们没有测试到。由于行覆盖率比函数和类覆盖率更精细,因此实际应用中,我们使用行覆盖率作为覆盖标准,虽然它不比路径覆盖在覆盖程度上更加全面,但也能很大程度上反应测试可靠性,补充case,降低漏测。
接下来我们看下报告结果的展示,下图为总覆盖率以及每个文件的覆盖率情况
图片
下图为每个文件的覆盖详情,绿色代表测试时执行到的代码,红色代表没有执行到的代码,此图代表只测了符合if条件的场景,其他场景没有覆盖到
图片
完成了工具的二期开发工作后基本满足了我们的使用需求,但是整个流程操作起来还比较复杂,学习成本比较高,需要记忆各种文件的位置以及手动操作很多脚本,这对我们工具的使用和推广非常不利,因此我们将脚本平台化提供了操作页面,来降低使用成本,目前使用平台只需要操作三步,即可完成一次测试覆盖率报告的生成:    
  • 一键部署测试环境:添加机器ip和地址信息,自动从git拉取工具代码,以及配置xdebug扩展
  • 配置提测分支并开启覆盖率统计图片
  • 测试完成后,结束统计并生成报告图片
现在操作成本高的问题也解决了,接下来就要开始推广使用了,为了避免被全组人喷,先在QA小组内试用一下,有bug偷偷改了,也不至于太丢面子O(∩_∩)O哈哈~。经过一段时间的试用,果然发现一些问题,除了一些小功能和易用性问题,还发现了阻碍我们推广事业发展的两个性能问题以及diff文件生成方式的功能问题,由于经常会收到大家反馈的性能问题,所以不得不对工具底层进行优化了。    

4、工具的升级改造  

我们先来分析一下在实际的应用过程中遇到的几个问题
问题一:开启代码覆盖率后,接口响应慢,有的接口甚至超时报504。
原因分析:
收到这个问题反馈之后我们使用问题接口进行调试,最终发现原来是xdebug的锅,开发环境开启xdebug后,某些逻辑复杂的接口响应耗时竟然从1s增加到10s,那为会有如此惊人的耗时呢,除了上面介绍的原理中它在执行过程中会重写php函数外,它还有一些其他的功能会在代码执行过程中运行,比如他还有用于 IDE 的单步调试器,并配备了探查器、升级等功能。由于它以不同的方式执行更多操作,因此在收集代码覆盖率方面较慢。这个问题不仅会影响我们的测试效率,还会影响我们对接口本身性能的判断,所以必须进行优化。
解决方案:
针对这个问题,我们首先想到如果关闭xdebug的其他功能,只保留覆盖率统计功能是否可行?不过这个难度感觉有点高了,或者有没有其他性能更好的扩展来代替xdebug呢?经过调研发现,PHPUnit 8x之后的版本不紧支持xdebug,还支持了pcov插件作为code coverage driver,而pcov不像xdebug有很多其他的功能,它专门用来统计覆盖率,因此他的性能要远远高于xdebug,而且在实际测试中也证实了这一点,因此我们决定对工具底层进行升级改造,使用pcov插件代替xdebug。
而网上关于pcov的介绍以及应用相比php-code-coverage更是少之又少,在改造的过程中仍然没有有效的可复制的方案和技术来参考,我们只能在摸索中前进。
由于我们之前使用的php-code-coverage版本是6.1.4版本,里面集成的phpunit版本是7.5,如果要引入pcov,需要8.0以上的版本,因此进行升级改造有两种方案:
  1. 使用现有php-code-coverag版本,保留之前二次开发的功能,直接切换底层扩展为pcov,改造处理收集的覆盖率文件功能以及生成报告功能使之兼容pcov格式。    
    结论:不可行
    原因:当前版本的文件结构与集成了phpunit8.*的php-code-coverage版本相差甚远,如果把pcov相关的功能从新版本拷贝过来,熟悉源码和调试的成本比较高。
  2. 升级php-code-coverage版本,将在老版本中开发的功能迁移至新版本中,并改造处理收集的覆盖率文件的功能以及生成报告的功能使之兼容pcov格式,并生成报告。
方案二相对方案一来说成本相对较低,因此我们采用方案二进行升级改造。但此方案还存在一个版本兼容问题,即phpunit8兼容的PHP版本为PHP7.2,PHP7.3,PHP7.4,但我们开发机的PHP版本是PHP 7.1.10,版本不兼容,导致新版本的phpunit直接部署到开发上无法运行,因此需要针对php>7.1的不兼容语法进行修改,使之成功运行图片
问题二:测试完成后,生成报告的接口超时
原因分析:由于目前实现逻辑中每调用一次接口或运行一次脚本都会生成一个覆盖率文件,导致大的项目测试完成时生成的文件比较多,而导致最终根据文件生成报告时分析超时,生成报告失败
解决方案:将每次生成的覆盖率结果追加写入到一个文件中,最后生成报告时对一个文件进行处理和分析。
问题三:有的项目部分文件代码覆盖率统计不到
问题分析:获取增量代码时生成diff文件时我们使用的是gitlab提供的api接口,但是这个接口的获取结果受文件限制的约束,导致新分支代码修改过多时获取不到完整的diff内容。
限制说明:gitlab会限制单个文件和整个差异的大小,在某些情况下,会折叠差异文件,或者根本不展示。比如修改文件超过100个、单个文件渲染超过5000行、文件大小超过500KB等,这些场景下超过的部分都将被折叠或不展示。
解决方案:与负责维护gitlab的运维同事沟通后,由于安全问题,此限制不建议打开,因此我们采取最基本的git diff命令获取每个模块的diff详情,生成最终的diff文件,由于接口获取到的diff结果与该命令生成的结果格式不同,因此还需要对解析diff文件的代码进行改造。


四、总结  

通过以上的方案对代码升级改造后,我想再也没有什么能够阻挡我们推广代码覆盖率工具的脚步了。目前代码覆盖率已经是智域项目流程中不可或缺的一部分,不仅用于qa上线之前补充完善case避免代码漏测,而且也加入了rd的提测流程,要求rd提测前覆盖率达到80%才允许提测,具体项目流程如下:
图片
从推广使用到目前为止:
  1. 90%的C级项目都会通过该工具成功生成覆盖率报告;
  2. 该工具也用于统计接口自动化的覆盖情况,来逐步提升接口自动化的覆盖率,目前分拣、下call等重要接口自动化覆盖率已达到70%以上;
  3. 在重构或迁移型项目上线前结合流量回放,做功能测试和回归,在提升效率的同时,提高测试覆盖避免遗漏;
   


继续滑动看下一个
北京顺丰同城科技技术团队
向上滑动看下一个