完美搞定hyperf的单元测试
最近团队决定要统一使用hyperf。工欲善其事,必先利其器。要提高开发效率,TDD必不可少,这就需要能有一套完美的单元测试支持工具。相比传统的php开发,hyperf基于swoole,需要满足其不同的特征,要想完美地搞定其单元测试,需要完成这么几件事情:
- phpunit需要支持协程,才能正常运行下去
- swoole需要支持openssl,才能支持对api接口进行单元测试
- 支持单步跟踪。这点需要有xdebug的支持,同样,它也要支持协程
- 支持单测覆盖率。
下面来分别介绍如何解决这几个问题。
1. phpunit支持协程
在 Hyperf 里测试默认通过 phpunit 来实现,但由于 Hyperf 是一个协程框架,所以默认的 phpunit 并不能很好的工作,因此hyperf提供了一个 co-phpunit 脚本来进行适配,可以直接调用脚本或者使用对应的 composer 命令来运行。
在phpstorm里边,配置phpunit位置时,可以直接指定到项目的vendor/bin/co-phpunit即可。
2. swoole支持openssl
这是因为在我们的业务代码中,会访问第三方的网站,需要支持通过https协议访问网站。
这需要在编译swoole插件的时候,通过--enable-openssl
选项启用openssl的支持。另外,在mac通过brew安装的openssl,有可能不在默认位置,因此还需要通过选项--with-openssl-dir
指定openssl目录。
./configure --with-php-config=/usr/local/Cellar/php\@7.3/7.3.14/bin/php-config --enable-openssl --with-openssl-dir=/usr/local/Cellar/openssl\@1.1/1.1.1d/ ./configure
3.支持单步跟踪
在swoole的官网文档,已经明确指出xdebug插件可能造成swoole的不稳定,不予支持。但也给出建议,可以使用sdebug (https://github.com/mabu233/sdebug) 作为替代品。
按照readme进行sdebug插件的安装即可。
4.支持测试覆盖率
这一块目前尚在研究中,虽然phpunit.xml中配置了覆盖率的相关配置,但是运行后还是提示:
Error: No code coverage driver is available
在sdebug的readme上有说明:
为了避免 swoole 的检测 xdebug 警告, 扩展注册的名称是 sdebug, 如果想使用 Phpunit CodeCoverage , 需要手动把检测 xdebug 的判断修改成 sdebug。
但是,具体如何做并没有给到详细的讲解。我们自己来搞定好了。
# 进入vendor目录 $ cd vendor $ grep 'No code coverage driver is available' ./ -rn -C 2 grep 'No code coverage driver is available' ./ -rn -C 2 .//phpunit/phpunit/src/TextUI/TestRunner.php-484- .//phpunit/phpunit/src/TextUI/TestRunner.php-485- if ($codeCoverageReports > 0 && !$this->runtime->canCollectCodeCoverage()) { .//phpunit/phpunit/src/TextUI/TestRunner.php:486: $this->writeMessage('Error', 'No code coverage driver is available'); .//phpunit/phpunit/src/TextUI/TestRunner.php-487- .//phpunit/phpunit/src/TextUI/TestRunner.php-488- $codeCoverageReports = 0;
由上述代码可知,关键还在于canCollectCodeCoverage
这个函数的判断。我们再来找函数的定义位置:
grep 'function canCollectCodeCoverage()' ./ -rn -A 4 .//sebastian/environment/src/Runtime.php:26: public function canCollectCodeCoverage(): bool .//sebastian/environment/src/Runtime.php-27- { .//sebastian/environment/src/Runtime.php-28- return $this->hasXdebug() || $this->hasPCOV() || $this->hasPHPDBGCodeCoverage(); .//sebastian/environment/src/Runtime.php-29- } .//sebastian/environment/src/Runtime.php-30-
从上述代码可知,关键在于 hasXdebug
方法的判断。再次查找:
$ grep 'function hasXdebug()' ./ -rn -A 4 .//sebastian/environment/src/Runtime.php:174: public function hasXdebug(): bool .//sebastian/environment/src/Runtime.php-175- { .//sebastian/environment/src/Runtime.php-176- return ($this->isPHP() || $this->isHHVM()) && \extension_loaded('xdebug'); .//sebastian/environment/src/Runtime.php-177- } .//sebastian/environment/src/Runtime.php-178-
这次,我们终于找到问题的源头了,修改sebastian/environment/src/Runtime.php
文件的第176行为下面的代码即可。
return ($this->isPHP() || $this->isHHVM()) && (\extension_loaded('xdebug') || \extension_loaded('sdebug'));
然后,我们继续运行phpunit,会发现报下面的错误:
PHP Fatal error: Uncaught SebastianBergmann\CodeCoverage\RuntimeException: This driver requires Xdebug in /Users/ronnie/PhpstormProjects/code/location-services/vendor/phpunit/php-code-coverage/src/Driver/Xdebug.php:38
打开该文件,发现第38行位置代码如下:
if (!\extension_loaded('xdebug')) { throw new RuntimeException('This driver requires Xdebug'); }
同理,修改第37行代码即可:
if (!(\extension_loaded('xdebug') || \extension_loaded('sdebug'))) {
然后再次运行phpunit,终于可以运行完成测试覆盖率的生成了。
至此,我们终于能宣告完美支持hyperf的单元测试了。