我又来水文章了。
今天要写的是给php的Phalcon框架搭建单元测试环境碰到的一系列问题。 标题有标题党的嫌疑,并且不同知识背景下,对这些问题可能看法不同。
下面开始整个环境搭建流程:
开发环境Phalcon的版本是3.3,正好网上有3.4的中文文档,直接按着链接的方法把目录和php文件都创建好了。
php并没内置单元测试的类库,所以要写单元测试需要先安装phpunit,现在的框架都是使用composer进行依赖管理,所以很方便,执行以下命令安装:
composer require phpunit/phpunit
安装完了,切换到指定目录下,也就是文档中的tests目录,直接执行phpunit
就可以看到结果了,如果要在其他目录执行需要手动指定配置文件,例如:phpunit -c ./tests/phpunit.xml
。
正常情况下结果应该是类似这样的:
Time: 434 ms, Memory: 6.00 MB
OK (2 tests, 3 assertions)
然而第一个问题出现了:
Fatal error: Declaration of Phalcon\Test\PHPUnit\UnitTestCase::setUp() must be compatible with PHPUnit\Framework\TestCase::setUp(): void
这个问题很明显是setUp
方法与父类不一致,需要添加返回类型声明(PHP7)的新特性。
这个问题经过查询,有两种解决办法,一个是phpunit降级,另一个是自己改代码。
第一种方式:最新安装的phpunit版本是8.5,安装7.0版本就可以了:
composer remove phpunit/phpunit
composer require phpunit/phpunit ^7.0
第二中方式:手动添加返回类型声明,修改文档中的UnitTestCase.php
脚本的setUp
方法:
public function setUp(): void
然后再执行,还有一样的错误,看代码是UnitTestCase.php
继承的PhalconTestCase
有一样的问题,这个基类是通过原文这段话介绍安装的:
为了帮助您构建单元测试,我们制作了一些抽象类,您可以使用它们来引导单元测试本身。这些文件存在于Phalcon Incubator中。 您可以通过将Incubator库添加为依赖项来使用它:
composer require phalcon/incubator
直接看代码,根据继承关系也找到了另一个setUp
方法,也做同样的修改,问题解决。
第二个问题开始了:
再执行phpunit
没有报错了。但是没有执行测试用例,输出如下:
Time: 259 ms, Memory: 4.00Mb
No tests executed!
测试用例写的好好的,为什么没执行呢?一点思路也没有直接放狗查,还真的很容易就找到了。
采纳答案的描述没看太懂,但是的确解决了我的问题。用./vendor/bin/phpunit
代替全局的phpunit
。
在执行一下,终于看到了绿色(从未像现在这样期待绿色,前提是xml里面开启了colors,哈哈):
Time: 385 ms, Memory: 6.00 MB
OK (1 test, 2 assertions)
对环境没有依赖的测试用例已经可以正常执行了,但是实际工作中通常都会涉及到数据库等外部资源,不再只是简单的计算了,所以需要把环境初始化完整。
跟所有的MVC框架一样,Phalcon也有一个单入口的index.php文件执行http请求,cli程序也一样需要一个单入口文件来初始化基境,所以很容易的想到在单元测试的入口文件处添加依赖。
其实文档里面已经帮我们创建了,就是TestHelper.php
,其实UnitTestCase.php
的setUp
方法也是可以的,但是因为初始化环境通常得include许多文件,放在setUp
里面不太好,所以就准备写在TestHelper.php
里。
基于上面的分析,直接复制index.php
里面的代码就差不多了,因为目录不一样注意一些常量的赋值即可。
复制过来之后,简单的对一个数据库查询做了单元测试。
然而就出现了第三个问题:
Phalcon\Di\Exception: Service 'modelsManager' wasn't found in the dependency injection container
很纳闷呀,index.php
里面的代码都已经复制过来了,并且没有报错说明是正常执行了的,怎么就没有注入呢?
然而我查呀查始终也找不到原因,中间也尝试过手动注入服务进去(如下代码),但是又会产生新的问题(这里就不贴了,只是换了个Service,问题是一样的),而且我想index.php
是正常的,一定不会少注册服务,所以肯定是哪块有问题了,暂时也没思路,放弃了。。。
$di->set(
"modelsManager",
function() {
return new ModelsManager();
}
);
饭后,我想着没啥事再看看吧。
首先看TestHelper.php
,代码最下面是这样的:
Di::setDefault($di);
没有看过源码,做的是大概是:di变量是依赖注入的容器,说白了应该是关联数组,setDefault方法类似是一个单例,其他地方直接get出来在这里初始化好的容器。
这样分析的话,TestHelper.php
应该是正常的,继续往下分析,到了UnitTestCase.php
的setUp
方法:
parent::setUp();
$di = Di::getDefault();
$this->setDi($di);
如前所述,这个方法用如下代码把容器给获取出来,并设置成了属性,单看后两句应该也没啥问题,那看看parent::setUp()
:
protected function setUpPhalcon()
{
$this->checkExtension('phalcon');
// Reset the DI container
Di::reset();
// Instantiate a new DI container
$di = new Di();
// Set the URL
$di->set(
'url',
function () {
$url = new Url();
$url->setBaseUri('/');
return $url;
}
);
$di->set(
'escaper',
function () {
return new Escaper();
}
);
$this->di = $di;
}
上面代码,是parent::setUp()
最终调用的代码,可以猜测是官方把我们之前setDefault的容器给reset掉了,哭笑不得ing。
鉴于官方这段代码也没做什么事,简单粗暴的处理办法是直接注释掉,UnitTestCase.php
的setUp
方法只保留下面这段代码:
$di = Di::getDefault();
$this->setDi($di);
$this->_loaded = true;
应该正常了吧,然而还是有问题:
TypeError: Argument 1 passed to Phalcon\Test\PHPUnit\UnitTestCase::setDI() must implement interface Phalcon\DiInterface, null given, called in xxx
这个为题是因为$this->setDi($di);
,源代码中被跳过的部分可以看到$di = new Di();
,这里的di是Di类的实例
,而现在报错的di是FactoryDefault类的实例
。
解决报错的方便简单粗暴可以把官方代码的参数类型声明去掉,也即:
public function setDI(DiInterface $di)
改为:
public function setDI($di)
这样就可以愉快的玩耍了,简直累死个人。
结束语
Phalcon这个框架相对来说还是小众了一点,国内的资料很少。但是官方的交流群还是不错的,虽然这几个问题没有一个是在官方找到答案的。
写完文章脖子都疼了,又是没啥技术含量的水文,哈哈,睡觉😴
(完)
- 本文作者:吴泽辉
- 本文链接:https://mutex.top/posts/e82ad42a/
- 发表日期:2020年3月7日
- 版权声明:本文章为原创,采用《知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议》进行许可