一、写在最前
这篇文档是我对之前一段时间工作的总结和分享,自己也是第一次涉猎这方面的知识,肯定有遗漏和偏差,甚至“低级错误”,所以想起到抛砖引玉的作用,大家互相分享,共同进步。能够给同学们的工作带来些许启发,我也就很满足了。
二、覆盖率是什么?它能反映什么?
简单的说,覆盖率通常是指程序的执行过程中(即一个case),已执行的代码与可执行的代码的比值(或者与总代码行数的比值)。它能够从一个侧面反映出case的质量,即case是否做到对代码的完全覆盖。
三、PHP如何测试覆盖率?
1. Xdebug
Xdebug是PHP的一个扩展,了解PHP的同学一定不会对它陌生,非常强悍的调试助手,默认并没有开启,需要另外安装,不过多数情况下只需要在php.ini配置文件中开启即可。成功开启Xdebug后,我们便可以在程序中使用以下几个函数:
xdebug_start_code_coverage() // 作用为开始统计覆盖率 xdebug_get_code_coverage() // 作用为获取当前已统计信息 xdebug_stop_code_coverage() // 作用为结束覆盖率统计
下面是个简单的例子:
<?php xdebug_start_code_coverage(); // 注释、空行不会统计 echo ‘hello world!‘; if ( false ) { echo "here is wrong\n"; } else { echo "here is rigth\n"; } $data = xdebug_get_code_coverage(); xdebug_stop_code_coverage(); // stop之后也不会统计
$data中保存了覆盖率信息,结果为:
1 ( 2 [/Users/liufuxin/www/mars/index.php] => Array 3 ( 4 [4] => 1 5 [6] => 1 6 [9] => 1 7 [12] => 1 8 ) 9 )
可以看到,$data中保存了文件的相关信息,文件中第4、6、9、12行代码此次被执行了(状态码1表示执行)。本质上来说,基于这些信息,我们便可以统计代码的覆盖率了。各种框架也大多是基于Xdebug所获得的信息进行深加工。
但Xdebug的功能远非如此,再简单介绍下Xdebug的其他几个常用功能:
1.1 获取当前运行的文件、行号以及方法
<?php function fix_string() { echo "Called @ ". xdebug_call_file() . ":" . xdebug_call_line() . " from " . xdebug_call_function(); } fix_string(); // out : Called @ /Users/liufuxin/www/mars/index.php: 8 from {main}
1.2 获取程序当前已运行时间
<?php echo xdebug_time_index(), "\n"; for ($i = 0; $i < 250000; $i++) { // do nothing } echo xdebug_time_index(); ?> // out // 0.0014429092407227// 0.015676975250244
1.3 追踪代码执行路径
可以在Xdebug的配置中将xdebug.auto_trace设置为on开启该功能,或者在程序中使用函数组xdebug_start_trace()和xdebug_stop_trace()来指定追踪的代码段。会自动在配置的输出目录中生成追踪文件,更多配置
1.4 Xdebug输出文件解析
当开启Xdebug后,会在指定的目录生成以“cachegrind.out.”开头的程序运行信息文件。它大致看起来是这样的:
version: 1 creator: xdebug 2.2.3 cmd: /Users/liufuxin/www/mars/index.php part: 1 positions: line events: Time fl=php:internal fn=php::xdebug_time_index 2 2 fl=php:internal fn=php::xdebug_time_index 6 0
这时候我们就需要一些解析工具,推荐使用PHP实现的解析器webgrind,它安装简单,从浏览器打开后会自动探测xdebug输出目录,然后输出解析结果:
可以清楚的看到程序执行过程中涉及的函数与其执行次数、耗时等基本信息。
2. PHPUnit
属于XUnit家族系列,用于对php代码进行单元测试,基于Xdebug可以方便快捷的对代码进行覆盖率测试,并生成直观的报表。看以下一个简单的例子,
准备两个类文件,BankAccount与对应的测试类BankAccountTest,代码如下:
<?php class BankAccount { protected $balance = 0; public function getBalance() { return $this->balance; } protected function setBalance($balance) { if ($balance >= 0) { $this->balance = $balance; } else { throw new BankAccountException; } } } ?>
<?php require_once ‘BankAccount.php‘; class BankAccountTest extends PHPUnit_Framework_TestCase { protected $ba; protected function setUp() { $this->ba = new BankAccount; } public function testBalanceIsInitiallyZero() { $this->assertEquals(0, $this->ba->getBalance()); } } ?>
然后在终端使用phpunit命令执行测试文件,并指定为html输出格式和输出文件report。
phpunit --coverage-html ./report BankAccountTest.php
执行完毕后,通过浏览器查看report文件,便可以清晰的查看代码执行情况:
3. codespy
与之前介绍的工具不同,codespy是纯php开发的轻量级覆盖率统计工具,并不依赖Xdebug。只需要在被测试代码前引入其库文件,便会自动在脚本执行完毕后生成测试报告。该工具是github上托管的开源工具(github大法好!)。这次我们以常用的PHP框架Thinkphp为例,在入口文件中引入库文件,同时进行一次API访问,查看生成的测试报告。入口文件如下:
<?php include ‘codespy.php‘; \codespy\Analyzer::$outputdir = ‘/Users/liufuxin/www/codespy‘; \codespy\Analyzer::$outputformat = ‘html‘; if(version_compare(PHP_VERSION,‘5.3.0‘,‘<‘)) { die(‘require PHP > 5.3.0 !‘); } define(‘APP_DEBUG‘,true); define(‘NO_CACHE_RUNTIME‘,True); require ‘./protected/ThinkPHP/ThinkPHP.php‘; ?>
Codespy使用起来非常方便,只需将库文件包含进来,同时设置报告的格式以及输出目录即可。然后我们访问API,查看报告。下图是本次访问所涉及的全部文件,大部分为框架代码:
我们只关注访问API的情况,点击具体Action查看详情:
可以看出来,报告高亮的显示了执行的代码,以及两个维度的覆盖率,即6.78%的语句覆盖率(statement coverage)与2%的行覆盖率(line coverage)。
4. PHPCoverage
河图上的工具,应用场景主要为满足页面级自动化测试,统计覆盖信息并生成报告,详情。可惜的是并没有提供任何文档,接口人也已离职,代码始终无法正常运行,不过通过查看其源代码,可以发现这个工具是基于phpcoverage这个开源项目进行的二次开发。需要依赖Xdebug和XML_Parser两个扩展。
5. Pika
同样为河图上的工具,特色是支持手工测试和生存周期控制,详情。其大致原理为在测试机安装并运行Pikagent程序,其可以与服务器进行交互,QA能够通过服务器的web界面控制整个测试流程,如下图所示:
详细阅读了该项目从最初的调研,到后期各种会议的文档,项目最早的目标便是实现百度PHP代码的覆盖率测试工具,希望能够结合手动与自动两种工作方式,同时实习测试周期的可控,即报告中可以包含多个case的运行情况。
但目前核心的中控机也已无法访问,项目也宣布停止维护,比较可惜,不过从他的文档中也可以吸收不少借鉴,比如对我厂及国内当时PHP覆盖率方面的调研(2009年),关于报告生成、性能优化、交互设计等都有一定的讨论和记录。
四、主要方案比较
1、方案总体比较
方案 |
缺点 |
优点 |
Xdebug |
1. 需要额外安装扩展 2. 输出解析复杂 |
1. 性能最优 2. 代码插入灵活 3. 功能丰富 |
PHPUnit |
1. 需要额外安装扩展 2. 必须使用PHPUnit框架重新编写case 3. 只能进行单元测试 |
1. 规范化 2. 结果解析最优 |
codespy |
1. 运行速度最慢 2. 功能单一 |
1. 代码插入简单 2. 开源,易于扩展 |
2、性能对比
更多的时候我们是不希望重新编写case和额外的代码,大家的测试场景也主要集中在API测试,PHPUnit更适合RD进行单元测试,所以本文将通过一个小实验比较下Xdebug和codespy的性能。
首先,我们以Thinkphp为例,计算两者在进行一次覆盖率统计中的耗时。访问一个空置的接口,并统计平均耗时。
方案 |
实验次数 |
平均时间(秒) |
Xdebug |
100 |
0.05 |
codespy |
100 |
0.91 |
从表中可以看到,Xdebug基本不会造成太大的性能损耗,而codespy却有将近20倍的额外耗时!
真的相差这么多?带着疑问,进行了另一个对比测试,这次我们在API中进行一次比较耗时的操作,从远程接口获取数据(几百数量级)解析并写入数据库,结果如下:
方案 |
实验次数 |
API耗时(秒) |
平均时间(秒) |
Xdebug |
100 |
4.42 |
4.48 |
codespy |
100 |
4.51 |
5.41 |
比较意外,两者的性能损耗并不是线性增长的,Xdebug的时间基本保持在0.05秒左右,而codespy则基本在1秒左右。但是codespy比xdebug性能更差是可以确定的,而codespy在实际生产环境中表现到底如何,会在实际使用过后再作分享。
综上,不同的方案有其所适合的不同场景。Xdebug适用于测试需求复杂的大型项目,例如函数覆盖、类覆盖等,同时其也很容易与第三方工具交互;PHPUnit主要用于模块的单元测试,同时其规范的case管理也适合大型项目;codespy以其轻量级与简单易扩展,能够胜任大多数的小项目的覆盖率测试需求。
五、最后
以上便是我经过调研学习的一点收获,希望能够对大家起到一定作用,欢迎拍砖。另外值得一提的是,刚开始还是想到Hetu上搜搜,结果比较失望,仅有的几个工具不是“年久失修”,就是没有文档,上手困难,希望百度人能够更好的总结分享,维护好我们的知识储备。
六、最最后
下面贴出一些相关的资源,方便大家搭建环境,查阅文档。
PHPUnit安装,Mac请看这里,主要介绍PHPUnit如何安装。
PHPUnit实例,PHPUnit的一篇更详细的介绍日志。
PHPUnit在线文档,中文在线文档,非常详细。
Xdebug配置,关于Xdebug的详细配置介绍。
Codespy fork,codespy的github地址。