当前位置: 首页 > 科技观察

Web端PHP代码功能覆盖测试解决方案

时间:2023-03-13 01:06:14 科技观察

1.关于代码覆盖率代码覆盖率的衡量有很多层次,比如行覆盖率、函数/方法覆盖率、类覆盖率、分支覆盖率等等。代码覆盖率也是衡量测试质量的重要标准。对于黑盒测试,如果你不确定你的测试用例是否真正跑遍了系统中的每一行代码,你将永远不得不在测试的完整性上做出妥协。因此,业界几乎每一种编程语言都有自己的一套代码覆盖率解决方案。PHP,世界上最美丽的语言,当然也不例外。PHPUnit和SpikePHPCoverage提供了一套基于xdebug的代码覆盖测试方案。在这篇文章中,我将针对我遇到的具体业务场景,谈谈我的PHP代码功能覆盖测试的解决方案。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]完整的测试过程如下:插桩是指在测试执行前的一些准备工作。3.函数覆盖解决方案(1)原理xdebug天生就提供了对行覆盖的支持,我们需要自己计算函数覆盖。函数覆盖需要两点数据,一是执行了哪些函数,二是文件中有多少个函数。文件中的函数总数,由于我们不可能执行所有的函数,这部分只能通过静态扫描代码来实现。如果是C++或者Java,可能需要一个词法分析工具,但是在PHP这门最美的语言面前,我们根本不需要那么复杂。从PHP4.3开始,PHPZendEngine内置了tokenizer功能,帮助开发者做源码词法分析。我们只需要在PHP中定义函数时找到对应的词法规则,就可以轻松获取指定PHP文件中的所有函数。tokenizer定义的接口也很简单:arraytoken_get_all(string$source)这个函数进行文件解析,将php源码拆分成一个由token组成的数组。stringtoken_name(int$token)将整数形式的令牌转换为字符串形式。类似于C语言中的strerror函数。借助tokenizer,您可以根据PHP函数定义的规则和格式设计一个有限状态机,完成对所有函数的解析。这部分代码,我写的比较简单,单独拿出来,仅供大家参考:PHPFunctionParser寻找函数覆盖率的另一个难点是获取执行函数列表。这个地方让我们少走了一些弯路。一开始最简单的方法是,由于我们是通过xdebug得到执行的行,所以我们可以通过行号来推断这一行是属于哪个函数。但是每次请求获取的行号信息量非常大。如果一个请求执行1000行,就要进行1000次判断,效率会比较差。研究了一下,发现xdebug提供了一个函数trace函数,可以获取到一个请求中的函数调用关系,但是我获取到了函数名,却没有办法获取到它所在的文件。所以,经过一番研究,我找到了Reflection。给定一个方法名和一个类名,倒过来可以找出它定义在哪个文件中。所以我们使用functiontrace将函数调用关系暂存到一个临时文件中,然后解析该文件得到执行的函数name(如果是类方法,则为“类名::函数名”的形式),然后通过反射机制可以逆向定义这个函数的文件。我又一次体会到了世界上最美的语言的力量。(2)插件为了降低使用门槛,我们应该尽量少改动PHP源代码。xdebug收集信息的原理是分别调用xdebug_start_code_coverage和xdebug_stop_code_coverage来控制覆盖信息收集的起止,所以改源码在所难免。我们这里的解决方案是通过register_shutdown_function将xdebug_stop_code_coverage注册为php程序结束前必须运行的程序(类似于C语言中的atexit函数),封装成一个文件,然后在源码中require***line这个文件就可以了。如果你的PHP框架是CodeIgniter,所有的请求都有一个统一的入口index.php,那么你只需要改这个文件,只需要改一行源码!事实上,目前基本上所有的PHP框架都使用一个index.php文件作为所有请求的入口点。我们对源代码唯一的改动就是在入口文件index.php的***行添加了一句:而phpcoverage.php的核心代码逻辑大致如下:}register_shutdown_function('xdebugPhpcoverageBeforeShutdown');...xdebug_start_trace(开始...);//备注:上面的省略号表示非关键代码,这里不再展示(3)信息存储我们有一个函数覆盖测试的思路,利用xdebug的函数trace获取一个中所有函数的调用关系request,获取执行的一个函数,将所有的函数输出到文件中,通过文件解析反射得到执行函数的名称和函数所在的文件。将此信息存储在数据库或文件中。我们之前在试用Spike的时候,发现信息都是以xml格式存储在文件中的,数据冗余度很高。结果经过几次测试,文件已经很大了。这显然不是我们希望看到的。因此,在存储数据的时候,我们直接将数据序列化为json格式,将字符串存储在文件中,大大减小了文件体积。同时,我们通过请求来源的IP和日期来分别存储不同的文件。这样一来,每天每台机器的请求数据一目了然,又向“精准”方向迈进了一步,测试人员的每一次请求都能被精准监控。下图是我们在业务实践中收集的一些数据文件的截图:这样,对于任何一个IP的每一个web请求,它所涵盖的行和功能信息都会被记录在文件中。对于一般的项目测试,只有少数测试人员在使用,所以不需要考虑一些性能问题。4.ReportGeneration生成覆盖率数据的原理上面说了,但是目前我们得到的只是一个数据文件,如何将其汇总成一个完整的报告呢?这就需要我们写一个脚本来解析刚刚生成的数据文件。我们的做法是借用开源工具秒杀phpcoverage的模板,加上自己的代码逻辑,尤其是这个工具没有的函数覆盖率统计。我们测试的网页生成的报告如下:图中可以看到每个文件的行覆盖率、函数覆盖率、总覆盖率统计。如果需要更准确的数据,可以点击文件链接查看覆盖了哪些代码行(蓝色覆盖,红色未覆盖):5.综上所述,在业务测试中做web测试时,代码覆盖率是An衡量测试质量的重要指标。我们希望使用这种方法尽可能“准确”。测试执行后,我们可以准确的看到哪一行代码执行了,哪一行没有执行。分析未执行的原因,从而完善测试用例。使用工具的过程也很简单,instrumentation=>testing=>datacollection=>report。而且这种方案对业务代码的影响最小,只需要改动一行代码。即使中间出现问题,也可以快速将代码恢复到原来的样子。让测试放心,让开发放心。但是,还有一点需要强调的是,并不是把所有的代码都覆盖了,这就证明测试完成了。但如果没有覆盖,那一定是不完整的。所以这个方案最大的意义就是可以在测试中找到一些缺失的代码,发现一些问题。其实也可以帮助新员工了解整个项目的代码结构。对于我们的每一次浏览器请求,我们都可以清楚地知道服务器上运行了哪些代码。