菜单

Web端PHP代码函数覆盖率测试解决方案,PHP调试跟踪之XDebug使用总结

2020年2月10日 - 前端排行
Web端PHP代码函数覆盖率测试解决方案,PHP调试跟踪之XDebug使用总结

2. 业务背景

假设我们在线开发了一个网站,交给业务测试的同事去进行功能测试。那他们是怎么测试的呢?通常情况下,无非是开发人员把网站部署好了,然后测试人员把网上所有功能都试用一遍,包括一些异常使用情况。对于业务测试来说,只要我把所有的功能点都测了,把所有异常使用情况也测到了,那就完成了。但是对于开发来说,我比较好奇的是,你是否把我写的所有代码都跑到了?会不会存在一些代码,只有在很特殊的情况下才能触发,而你从来没有测到过这些情况?这时,可能就需要代码覆盖率来出马了。

其实我首先想到了xdebug来测试覆盖率,只需要两三个函数即可,如下:

xdebug_start_code_coverage(); //开始收集代码行覆盖情况

xdebug_get_code_coverage(); //获取截至目前所跑过的代码文件名和行号

xdebug_stop_code_coverage(); //停止收集代码行覆盖情况

xdebug提供的接口可以用于测试行覆盖率,这是否能满足要求呢?其实,行覆盖率颗粒度有点细,实际项目中,开发人员可能会对代码进行微调。比如,这次测试,你跑过了A.php文件的第10行,但是我有一天对A.php进行了微调,在A.php第9行和第10行之间又加了两行代码。于是,原来的第10行变为了第12行,而xdebug的行覆盖信息只记录了行号……这样之前的数据岂不是不准确了么。。。考虑再三,我觉得函数覆盖是个不错的颗粒度。在相对成熟的项目中,很少有大规模函数变动的情况。不过问题是,xdebug并没有提供函数覆盖的接口。

于是,我们现在碰到的场景是:

【1】希望测到某次测试中所覆盖的所有函数列表,知道这个项目总共有多少个函数,计算一下覆盖率是否足够高。

【2】测试完成之后,要生成一份覆盖率报告,将代码的覆盖情况可视化。

【3】完整测试的流程如下:

图片 1

其中插桩的意思是在测试执行之前的一些准备工作。

 

3. 函数覆盖率解决方案

(1)原理

xdebug天生提供了对行覆盖率的支持,我们要自己计算出函数覆盖率。函数覆盖率需要两点数据,一个是哪些函数被执行,一个是文件中总共有多少个函数。

文件中总共的函数量,由于我们不可能把所有函数都执行一遍,因此这部分只能通过代码静态扫描来实现。如果是在C++或者Java中,可能就需要词法分析工具了,然而在最美的语言PHP面前,我们完全不需要那么复杂。从PHP4.3开始,PHP
Zend
Engine中内置了tokenizer功能,帮助开发者做源码词法分析。我们只需要找到PHP中定义函数时所对应的词法规律,就可以轻松得到指定PHP文件中的全部函数了。

tokenizer定义的接口也十分简单:

array token_get_all (string $source)

该函数进行文件解析,将php源代码拆成由token组成的数组。

string token_name (int $token)

将整数形式的token转变为字符串形式。类似于C语言中的strerror函数。有了tokenizer,自己再根据php函数定义的规律和格式设计一个有限状态机,即可完成全量函数的解析。这部分代码,本人写了个比较简陋的,把它单独拿出来,仅供大家参考:PHPFunctionParser

求函数覆盖率的另外一个难点在于获取被执行的函数列表。这地方让我们走了一些弯路。一开始一个最简单的办法,我们既然通过xdebug拿到被执的行,可以通过行号来反推此行属于哪一个函数。然而每一次的请求获取的行号信息量是非常大的,如果一个求情执行了1000行,那就要进行1000次判断,效率上会比较差。调研了一番之后,发现xdebug提供了function
trace的功能,可以把一次请求中的函数调用关系获取到,只不过拿到了函数名字,却没办法得到它所在的文件。于是,再次调研一番,发现了Reflection,给定方法名和类名,可以反推出来它在哪个文件中定义。于是我们使用function
trace把函数调用关系暂存在一个临时文件中,然后通过文件解析,拿到执行的函数名(如果是类方法,则是“类名::函数名”的形式),再通过reflection机制反推出定义这个函数的文件即可。再次体会到了世界上最美语言的强大之处。

(2)插桩

为了降低使用门槛,我们尽可能少地改变PHP源代码为好。xdebug收集信息的原理是分别调用xdebug_start_code_coverage和xdebug_stop_code_coverage来控制覆盖率信息收集的开始和结束,因此不可避免地要改变源代码。此处我们的解决办法是,将xdebug_stop_code_coverage通过register_shutdown_function注册为php程序结束前必须要跑的一段程序(类似C语言的atexit函数),将其封装到一个文件中,然后在源代码第一行require这个文件即可。如果你的PHP框架是CodeIgniter这种所有请求都有一个统一入口index.php的框架,那就只需要改变这一个文件即可,对源代码只有一行的改动!实际上,目前基本上所有的PHP框架,都是以一个index.php文件作为所有请求的入口。

我们对源代码的改动只有入口文件index.php的第一行加入了一句话:

require_once "/file/path/to/phpcoverage.php"; ?>

而phpcoverage.php核心代码逻辑大致如下:

<?php
 ……
function xdebugPhpcoverageBeforeShutdown(){
 ……
 $lineCovData = xdebug_get_code_coverage();
 xdebug_stop_code_coverage();
 ……
 xdebug_stop_trace();
 ……
}
register_shutdown_function(‘xdebugPhpcoverageBeforeShutdown’);
……
xdebug_start_trace(……);
xdebug_start_code_coverage();
//备注:上面省略号表示非关键代码,这里就不展示了

(3)信息存储

我们的函数覆盖率测试有了思路,使用xdebug的function
trace获取一次请求中所有函数的调用关系,得到执行过的所有函数,输出到文件中,通过文件解析和reflection获得被执行的函数名和该函数所在文件。将这些信息存入数据库或文件即可。

之前试用Spike的时候,我们发现这些信息以xml格式存入文件,数据冗余度很高,导致几个测试下来,文件已经非常大了。这显然不是我们想看到的。因此在数据存储的时候,我们直接将数据做json格式的序列化,字符串形式存在文件中,大大减少了文件大小。与此同时,我们再通过请求来源的IP和日期作为分隔,分别存储不同的文件。这样,来自每个机器每天的请求数据都能一目了然,向着“精准”的方向又迈进了一步,可以对测试人员的每个请求做精确的监控。下图是我们在业务实践中搜集的部分数据文件截图:

图片 2

这样,来自任何一个IP的每一次Web请求,它所覆盖的行和函数信息,都会被记录到文件中。对于一般的项目测试中,也就只有几个测试人员在使用,所以不需要考虑一些性能问题。

Xdebug是一个开源的PHP程序调试工具,可以使用它来调试、跟踪及分析程序运行状态。当然,Xdebug需要结合PHP的编辑工具来打断点、跟踪、调试及分析,比较常用的PHP的Xdebug调试环境:Vim
+Xdebug。

5. 总结

业务测试中做Web测试时,对代码的覆盖率是衡量测试质量的重要指标。我们希望通过此方法做到尽量地“精准”,测试执行完后可以精确看到哪一行代码被执行过,哪一行没被执行过。分析没被执行过的原因,从而改进测试用例。使用工具的流程也很简单,插桩=>测试=>搜集数据=>出报告。并且此解决方案最大化地减少了对业务代码的影响,只需要改一行代码即可。即便中间出现了问题,也可以快速将代码恢复为原来的样子。让测试放心,让开发也放心。

不过,最后还需要强调的一点是,并不是说覆盖了所有的代码,就证明测试已经完整了。只不过没被覆盖的话,一定是不完整的。所以这个方案最大的意义在于能够发现测试中一些遗漏的代码,找到一部分问题。其实,它也可以帮助新来的员工理解整个项目代码结构,我们可以清晰的知道,自己的每一次浏览器请求,到底在运行服务器上的哪些代码。

} else {

4. 报告生成

上面讲了生成覆盖率数据的原理,不过我们至此获得的只是一份份的数据文件,如何汇总成一份完整的报告呢?这就需要我们自己来写一段脚本解析刚才生成的数据文件了。我们的做法是借鉴了开源工具spike
phpcoverage的模版,并加入自己的代码逻辑,特别是加入了该工具所不具有的函数覆盖率统计数据。我们自己测试的web页面生成的报告如下:

图片 3

图中可以看到每个文件的行覆盖率,函数覆盖率,还有总的覆盖率统计数据。如果需要更精确的数据,可以点进文件连接,查看到底覆盖的是哪些代码行(蓝色为覆盖,红色为未覆盖):

图片 4

//的每行代码时,都会产生一个结果码,如下:

1. 关于代码覆盖率

衡量代码覆盖率有很多种层次,比如行覆盖率,函数/方法覆盖率,类覆盖率,分支覆盖率等等。代码覆盖率也是衡量测试质量的一个重要标准,对于黑盒测试来说,如果你不确定自己的测试用例是否真正跑过了系统里面的每一行代码,在测试的完整性上总要打些折扣。因此,业界几乎对各种编程语言都有自己的一套代码覆盖率解决方案。世界上最美的语言PHP当然也不例外。PHPUnit和Spike
PHPCoverage提供了一套基于xdebug的代码覆盖率测试方案。在本文中,我将针对自己碰到的特定业务场景,讲述一下自己进行PHP代码函数覆盖率测试的解决方案。

Php代码:

xdebug.profiler_output_dir =”/php/ext/xdebug_profilers”

xdebug.collect_return = on

对于调试中的操作在下面附加上:

 

//该函数不返回任何值,用来停止覆盖分析,如果传入参数为true,那么就会停止分析并清空内存中的分析结果集,否者传入false,反之,还可使用//xdebug_start_code_coverage找回该内存信息。

C、示例的验证

// 定义一个测试函数

echo ‘判断是否开启…</br>’;

路径地址:$HOME(pwd ~)

 

$:Bp

在用户主目录下创建.vimrc文件:

unset($apiModel);

      // 开启覆盖分析

 $value = ‘马上使用XDebug调试程序,你准备好了吗’;

g:debuggerMaxDepth代表的是脚本调试的最大深度层次;

建议使用xdebug.profiler_enable_trigger替代xdebug.profiler_enable

coverageSample(10,10);

解决:

 

?>

 

四、注意事项

 

      function __construct() {

 

将上面下载的xdebug的plugin中文件复制到.vim下:

4、Xdebug的功能虽然强大,但是要均衡性能开销;

let g:debuggerPort = 9010(该端口必须与xdebug.remote_port相同)

A、配置文件配置不正确;

1、下载

xdebug.remote_autostart = on

 

  echo ‘覆盖分析已经完成</br>’;

·     调试环境

B、涉及的函数

 测试下是否正常显示。

 

 

注意:

echo ‘开启覆盖分析…</br>’;

 

Xdebug的PHP脚本分析功能比较实用,它可以帮助我们分析代码的瓶颈和影响性能缓慢的问题,为优化代码提供可行性的参考。

$ sudo mkdir ~/.vim

echo ‘覆盖分析进行中…</br>’;

 

下面罗列下,WebGrind的效果:

NOTE:

C、.vimrc和php.ini中的port与现有的port冲突;

类型

功能

说明

<Command Mode>

 

 

:Bp

 toggle breakpoint

断点标记

:Up

 stack up

 

:DN

 stack down

 

<Normal Mode>

 

 

,e

 eval

 

<Function Keys>

 

 

F1

 resize

调整窗口大小

F2

 step into

调试步进入

F3

 step over

调试步进入下一标记

F4

 step out

调试步出当前标记

F5

 run

调试运行

F6

 quit debugging

退出调试模式

F11

 get all context

获得所有变量内容

F12

 get property of cursor

获得当前光标变量

//该函数返回数组值,用来搜集和返回代码覆盖分析的结果集信息。

           returnjson_encode(xdebug_get_code_coverage());

使用vim普通模式打开php文件,移动鼠标箭头到欲调试的那行,输入:

 

xdebug_start_code_coverage(XDEBUG_CC_UNUSED|XDEBUG_CC_DEAD_CODE);

 

 

 

      private $_coverage_info;

截图如下:

xdebug.remote_port = 9010

XDEBUG_CC_UNUSED:用来计算分析时包含搜集未被执行的代码;

 

//该配置默认为1,也就是默认开启,如果设置为0,代码的覆盖分析就不会进行。

为.vimrc添加以下内容:

  echo ‘开启覆盖分析失败了</br>’;

一、安装配置

A、涉及的配置

C、示例的验证

该文件的内容不是很直观,所以需要使用可视化的工具来查看和分析,而Xdebug本身就支持使用第三方的可视化profiler文件的内容。在Linux下,可以使用KCacheGrind,而在Windows平台,可以使用QCacheGrind,当然还有一些在线的由爱好者开发的工具,例如:WebGrind,具体怎样使用这些工具,可以参考:

 

$ sudo vim ~/.vimrc

      // 分析是否执行

 

图片 5

xdebug.profiler_enable_trigger

          $this->_coverage_info = xdebug_get_code_coverage();

echo ‘获取分析结果…</br>’;

//该配置默认为0,为开启,设置为非0之后,即开启profiler功能

 

 

1、安装

      public functionxdebugStartCodeCoverage() {

$result = $apiModel->getCodeCoverageResult();

NOTE:

xdebug.trace_output_dir = “/tmp/ext/xdebug_traces”

  echo ‘开启覆盖分析已完成</br>’;

echo ‘测试函数开启…</br>’;

$apiModel->xdebugStartCodeCoverage();

        }

五、遇到问题

PHP调试跟踪之XDebug使用总结:

 

$ sudo vim

NOTE:

全局版本路径查看:

WebGrind可以在这里下载:

//1:代表代码已经执行;

class XdebugCoverageAnalysisModel {

echo ‘关闭分析开关…</br>’;

//如果开启该选项,则在每次请求中如果GET/POST或cookie中包含//XDEBUG_PROFILE变量名,则才会生成性能报告文件(前提是必须关闭

浏览器结果:

unset($result);

产生该问题的原因大致如下:

·     安装配置

 

            xdebug_start_code_coverage( -1 | -2 );

1、避免生产环境开启profiler和trace,只需开启远程调试;

问题:Error(“DbgProtocol instance has no attribute ‘stop'”,)

$status = $apiModel->xdebugCodeStarted();

 

 

Vim + Xdebug:

$ sudo touch /usr/share/vim/vimrc  ~/.vimrc

B、使用vim打开php文件

xdebug.coverage_enable=1

}

//xdebug.profiler_enable选项,否则该选项不起作用)。

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图