YII integrated unit testing and functional testing, implemented with phpunit and selenium. The author encountered a lot of trouble during the configuration process, which is recorded here.
Necessary concepts
selenium
Selenium is a well-known automated testing tool that can launch a local browser to complete testing, so it can be used to automatically test web projects. Selenium is divided into server and client. The server is developed using Java, so it requires a jdk. When the server starts, it will start an http service. The client initiates a test request to the server through http communication with the server. The service The client will automatically launch the browser to complete the test. Testers are responsible for writing client scripts that support most mainstream programming languages. Of course, this is actually due to the powerful power of the open source community, which has developed interface programs for selenium for different languages. The protocol between the server and the client The author did not study it because it is not important.
phpunit
phpunit is a testing framework and tool for the PHP language. Its framework is used when performing unit testing, and its tools are used when performing functional testing. Based on this testing framework, someone made a Selenium PHP interface program, which exists as an extension of PHPunit.
How to integrate YII framework
Yii has made some simple packages for testing based on phpunit. Therefore, when using Yii for testing, you need to rely on the above two.
Environment installation
Firefox
There are not many browsers that selenium-server can recognize. It seems to be IE and Firefox, so install the Firefox browser on OSX first. There is no big difference between installing a browser and general software installation, so I won’t go into details here.
JDK
Since selenium-server is developed using java, we need to install JDK first. Just search JDK on Baidu to download and install it. No more details.
selenium-server
First install the server version of selenium. Under osx, you can use brew to install, which is more convenient:
$ brew install selenium-server-standalone
Since the source of selenium-server is on googleapis, you need to bypass the wall to operate. In fact, if you don’t bypass the wall, other steps will be difficult.
Prompt after installation is complete:
To have launchd start selenium-server-standalone at login: ln -sfv /usr/local/opt/selenium-server-standalone/*.plist ~/Library/LaunchAgents Then to load selenium-server-standalone now: launchctl load ~/Library/LaunchAgents/homebrew.mxcl.selenium-server-standalone.plist Or, if you don't want/need launchctl, you can just run: selenium-server -p 4444
Here we are clearly told to start the server through the following command
$ selenium-server -p 4444
As you can see, usually selenium-server listens to port 4444. If you want to modify the port, the corresponding Yii configuration needs to be modified.
phpunit
Detour
Personally understood, phpunit is a collection of tools and frameworks. Tools are tools, and frameworks are frameworks. According to the documentation on the official website, the tool part of phpunit is released in the form of phar package, while the framework part is managed through pear. So let’s record these two concepts first. If you are not interested, you can skip this section.
phar is a PHP packaging solution. That is to say, a PHP program or PHP website can be packaged and distributed together, or even called as a functional module. Therefore, phpunit can completely package tool programs into phar. When executing phar, you usually need to use the php command.
$ wget https://phar.phpunit.de/phpunit.phar $ chmod +x phpunit.phar $ sudo mv phpunit.phar /usr/local/bin/phpunit $ phpunit --version PHPUnit x.y.z by Sebastian Bergmann and contributors.
Use the above command to download the executable file of phpunit. You can see that it is a phar package
pear is a PHP extension library system, because it was difficult to reuse PHP in the early days. Compiled languages are easier to reuse due to their compact and rigorous syntax. Because PHP is flexible and changeable, the learning cost for reuse is relatively high, so pear proposed a programming specification and distribution system to realize the reuse of PHP functions. Now it seems that pear has been replaced by composer (discussed below). But since ancient things have gone through a detour, you might as well write them down.
You can install pear like this on mac:
$ wget http://pear.php.net/go-pear.phar $ sudo php -d detect_unicode=0 go-pear.phar
As you can see, go-pear is also a phar, but it is a php script to install pear, which can be executed using the php command. During the installation process, you will be prompted whether to modify the php.ini file:
WARNING! The include_path defined in the currently used php.ini does not contain the PEAR PHP directory you just specified: </usr/share/pear> If the specified directory is also not in the include_path used by your scripts, you will have problems getting any PEAR packages working. Would you like to alter php.ini </etc/php.ini>? [Y/n] : Y php.ini </etc/php.ini> include_path updated. Current include path : .: Configured directory : /usr/share/pear Currently used php.ini (guess) : /etc/php.ini Press Enter to continue: The 'pear' command is now at your service at /usr/bin/pear ** The 'pear' command is not currently in your PATH, so you need to ** use '/usr/bin/pear' until you have added ** '/usr/bin' to your PATH environment variable.
From this tip we can know:
pear’s executable program is installed in /usr/bin/pear
Pear has a working directory which is /usr/share/pear. This working directory needs to be added to php.ini. If you let the installation program add it automatically, it will look like this:
;***** Added by go-pear include_path=".:/usr/share/pear" ;*****
When we use require and other functions in PHP that include other files, PHP will actually search include_path in addition to the current directory. This configuration shows that the program code installed through pear will be stored in the working directory, and php can be found. By default, there will be a System.php in the working directory, so the following code can work:
<?php require 'System.php'; ?>
Install using composer
Originally, phpunit could be installed through pear. However, times have changed. In the era when composer was very popular, phpunit also announced full support for composer and abandoned pear. The original installation method through pear did not work anymore. In the end, I had no choice but to use composer (there are so many package management tools that ten fingers are not enough, I will have the opportunity to do a horizontal comparison in the future).
First install composer, in bypass state:
$ brew update $ brew tap josegonzalez/homebrew-php $ brew tap homebrew/versions $ brew install php55-intl $ brew install josegonzalez/php/composer
The composer is now installed.
在项目的根目录下,创建一个composer.json,写入:
{ "require-dev": { "phpunit/phpunit": "4.7.*", "phpunit/php-invoker": "*", "phpunit/dbunit": ">=1.2", "phpunit/phpunit-selenium": ">=1.2", "phpunit/phpunit-story": "*" } }
上面的phpunit-selenium就是基于phpunit写的selenium客户端库,详见文后的参考资料。
然后在项目根目录下,执行
$ sudo composer install
composer会根据这个composer.json文件在根目录创建一个vendor目录,并将依赖的东西全部下载到这个目录中,其中vendor/bin下面有phpunit的可执行文件。
由于是Yii的项目,所以cd到/protected/tests目录下,执行如下命令即可启动默认的SiteTest.php里面的测试方法: (注意在执行前,保持selenium-server开启状态)
$ ../../vendor/bin/phpunit functional/SiteTest.php
会看到firefox会在执行过程中自动启动,并由如下日志输出:
PHPUnit 4.7.7 by Sebastian Bergmann and contributors. Warning: Deprecated configuration setting "selenium" used . Time: 11.52 seconds, Memory: 6.50Mb OK (1 test, 1 assertion)
phpunit工具程序会自动找到tests/phpunit.xml这个配置文件并根据此来进行某些配置,而Yii会利用phpunit和phpunit-selenium的框架来与selenium-server端通信,server端会启动浏览器,并将日志和结果等返回给客户端。整个过程大致就是这样的。
测试
测试是软件开发中必不可少的环节.无论我们是否意识到,在开发Web应用的时候,我们始终都是在测试的.例如, 当我们用PHP写了一个类时, 我们可能会用到一些注入 echo 或者 die 语句来显示我们是否正确地实现了某个方法;当我们实现了包含一套复杂的HTML表单的web页面时, 我们可能会试着输入一些测试数据来确认页面是否是按照我们的预期来交互的.更高级的开发者则会写一些代码来自动完成这个测试过程, 这样一来每当我们需要测试一些东西的时候, 我们只需要调用代码, 剩下来的就交给计算机了. 这就是所谓的 自动测试, 也是本章的主要话题.
Yii 提供的测试支持包括 单元测试 和 功能测试.
单元测试检验了代码的一个独立单元是否按照预期工作. 在面向对象编程中, 最基本的代码单元就是类. 因此, 单元测试的主要职责就是校验这个类所实现的每个方法工作都是正常的. 单元测试通常是由开发了这个类的人来编写.
功能测试检验了特性是否按照预期工作(如:在一个博客系统里的提交操作).与单元测试相比, 功能测试通常要高级一些, 因为待测试的特性常常牵涉到多个类. 功能测试通常是由非常了解系统需求的人编写.(这个人既可以是开发者也可以是质量工程师).
测试驱动开发
以下展示的便是所谓的 测试驱动开发 (TDD) 的开发周期:
构建测试环境
Yii 提供的测试支持需要 PHPUnit 3.5+ 和 Selenium Remote Control 1.0+.请参照他们提供的文档来安装 PHPUnit 和 Selenium Remote Control.
当我们使用 yiic webapp 控制台命令来创建一个新的 Yii 应用时, 它将会生成以下文件和目录供我们来编写和完成测试.
testdrive/
protected/ 包含了受保护的应用文件
tests/ 包含了应用测试
fixtures/ 包含了数据 fixtures
functional/ 包含了功能测试
unit/ 包含了单元测试
report/ 包含了 coverage 报告
bootstrap.php 这个脚本在一开始执行
phpunit.xml PHPUnit 配置文件
WebTestCase.php 基于 Web 的功能测试基类
如上所示的, 我们的测试代码主要放在 fixtures, functional 和 unit 这三个目录下, report 目录则用于存储生成的代码 coverage 报告.
我们可以在控制台窗口执行以下命令来执行测试(无论是单元测试还是功能测试):
% cd testdrive/protected/tests % phpunit functional/PostTest.php // 执行单个测试 % phpunit --verbose functional // 执行 'functional' 下的所有测试 % phpunit --coverage-html ./report unit
上面的最后一条命令将执行 unit 目录下的所有测试然后在 report 目录下生成出一份 code-coverage 报告. 注意要生成 code-coverage 报告必须安装并开启PHP的 xdebug 扩展 .
测试的引导脚本
让我们来看看 bootstrap.php 文件里会有些什么. 首先这个文件有点特殊,因为它看起来很像是 入口脚本, 而它也正是我们执行一系列测试的入口.
$yiit='path/to/yii/framework/yiit.php'; $config=dirname(__FILE__).'/../config/test.php'; require_once($yiit); require_once(dirname(__FILE__).'/WebTestCase.php'); Yii::createWebApplication($config);
如上所示, 首先我们包含了来自 Yii 框架的 yiit.php 文件, 它初始化了一些全局常量以及必要的测试基类.然后我们使用 test.php 这个配置文件来创建一个应用实例.如果你查看 test.php 文件, 你会发现它是继承自 main.php 这个配置文件的, 只不过它多加了一个类名为 [CDbFixtureManager] 的 fixture 应用组件.
return CMap::mergeArray( require(dirname(__FILE__).'/main.php'), array( 'components'=>array( 'fixture'=>array( 'class'=>'system.test.CDbFixtureManager', ), /* 去除以下注释可为测试提供一个数据库连接. 'db'=>array( 'connectionString'=>'DSN for test database', ), */ ), ) );
通过这样一个启动脚本, 当我们执行单元测试时, 我们便可以获得一个与服务需求类似的应用实例, 而主要的不同就是测试拥有一个 fixture 管理器以及它专属的测试数据库.
定义特定状态(Fixtures)
自动测试需要被执行很多次.为了确保测试过程是可以重复的, 我们很想要在一些可知的状态下进行测试, 这个状态我们称之为 特定状态. 举个例子,在一个博客应用中测试文章创建特性, 每次当我们进行测试时, 与文章相关的表(例如. Post 表 , Comment 表)应该被恢复到一个特定的状态下. PHPUnit 文档 已经很好的描述了一般的特定状态的构建. 而本节主要介绍怎样像刚才描述的例子那样构建数据库特定状态.
设置构建数据库的特定状态,这恐怕是测试以数据库为后端支持的应用最耗时的部分之一.Yii 引进的 [CBbFixtureManager] 应用组件可以有效的减轻这一问题.当进行一组测试的时候,它基本上会做以下这些事情:
在所有测试运行之前,它重置测试相关数据为可知的状态.
在单个测试运行之前, 它将特定的表重置为可知状态.
在一个测试方法执行过程中, 它提供了供给特定状态的行数据的访问接口.
请按如下使用我们在 应用配置 中配置的 [CDbFixtureManager].
return array( 'components'=>array( 'fixture'=>array( 'class'=>'system.test.CDbFixtureManager', ), ), );
然后我们在目录 protected/tests/fixtures下提供一个特定状态数据. 这个目录可以通过配置应用配置文件中的 [CDbFixtureManager::basePath] 属性指定为其他目录.特定状态数据是由多个称之为特定状态文件的PHP文件组合而成.每个特定状态文件返回一个数组, 代表数据的一个特定表的初始行.文件名和表名相同.以下则是将 Post 表的特定状态数据存储于名为 Post.php 文件里的例子.
<?php return array( 'sample1'=>array( 'title'=>'test post 1', 'content'=>'test post content 1', 'createTime'=>1230952187, 'authorId'=>1, ), 'sample2'=>array( 'title'=>'test post 2', 'content'=>'test post content 2', 'createTime'=>1230952287, 'authorId'=>1, ), );
正如我们所见, 上面返回了两行数据. 每一行都表示一个数组,其键是表的字段名,其值则是对应的字段值.每行的索引都是称之为行别名的字符串(例如: simple1, simple2). 稍后当我们编写测试脚本的时候, 我们可以方便地通过它的别名调用这行数据.你也许注意到了我们并未在上述特定状态中指定 id 字段的值. 这是因为 id 字段已经被定义为自增主键了,它的值也会在我们插入新数据的时候自动生成.
当 [CDbFixtureManager] 第一次被引用时, 它会仔细检查所有的特定状态文件然后使用他们重置对应的表.它通过清空表,重置表主键的自增序列值,然后插入来自特定状态文件的数据行到表中来重置表.
有时候,我们可能不想在一套测试前重置特定状态文件里描述的每一个表, 因为重置太多的特定状态文件可能需要很多时间.这种情况下,我们可以写一个PHP脚本来定制这个初始化过程.这个脚本应该被保存在存放特定状态文件的目录下,并命名为 init.php.当 [CDbFixtureManager] 检测到了这个脚本的存在, 它将执行这个脚本而不是重置每一个表.
不喜欢使用默认方式来重置表也是可以的,例如: 清空表然后插入特定状态数据. 如果是这种情况, 我们可以为指定的特定状态文件编写一个初始化脚本.这个脚本必须名称为表名+.init.php. 例如: Post 表的初始化脚本文件就是 Post.init.php. 当 [CDbFixtureManager] 发现了这个脚本,它将执行这个脚本而不是采用默认的方式去重置该表.
Tip: Too many specific state files greatly extend the test time. Therefore, you should only provide specific state files for those tables whose data will change during the test. Those tables that serve as lookup services will not change, so No specific status file required.