YII は、phpunit と Selenium で実装された単体テストと機能テストを統合します。著者は設定プロセス中に多くの問題に遭遇しました。これはここに記録されています。
重要な概念
セレン
Selenium は、ローカル ブラウザを起動してテストを完了できるよく知られた自動テスト ツールであるため、Web プロジェクトの自動テストに使用できます。 Selenium はサーバーとクライアントに分かれており、サーバーは Java を使用して開発されているため、サーバーが起動すると、クライアントはサーバーとの http 通信を通じてテスト リクエストを開始します。サービス クライアントはブラウザを自動的に起動してテストを完了します。もちろん、テスターは、ほとんどの主流のプログラミング言語をサポートするクライアント スクリプトを作成する責任があります。これは、さまざまな言語向けの Selenium のインターフェイス プログラムを開発したオープン ソース コミュニティの強力な力によるものです。著者は重要ではないので勉強しませんでした。
phpユニット
phpunit は、PHP 言語のテスト フレームワークおよびツールです。そのフレームワークは単体テストを実行するときに使用され、そのツールは機能テストを実行するときに使用されます。このテスト フレームワークに基づいて、PHPunit の拡張として存在する Selenium PHP インターフェイス プログラムが作成されました。
YII フレームワークを統合する方法
Yii は phpunit に基づいてテスト用のシンプルなパッケージをいくつか作成しました。したがって、Yii をテストに使用する場合は、上記の 2 つに依存する必要があります。
環境のインストール
Firefox
selenium-server が認識できるブラウザは IE と Firefox だけのようですので、まず OSX に Firefox ブラウザをインストールします。ブラウザのインストールと一般的なソフトウェアのインストールに大きな違いはないため、ここでは詳しく説明しません。
JDK
selenium-server は Java を使用して開発されているため、最初に JDK をインストールする必要があります。Baidu で JDK を検索してダウンロードしてインストールします。詳細は不要です。
セレンサーバー
まずサーバーバージョンの Selenium をインストールします。 OSX では、brew を使用してインストールでき、より便利です:
selenium-serverのソースはgoogleapis上にあるので、実際には壁を迂回して動作させる必要があります。
インストール完了後のプロンプト:
リーリーここでは、次のコマンドを使用してサーバーを起動するように明確に指示されています
リーリーご覧のとおり、通常、selenium-server はポート 4444 をリッスンします。ポートを変更したい場合は、対応する Yii 設定を変更する必要があります。
phpユニット
寄り道
個人的に理解すると、phpunit はツールとフレームワークのコレクションです。ツールはツールであり、フレームワークはフレームワークです。公式ウェブサイトのドキュメントによると、phpunit のツール部分は phar パッケージの形式でリリースされ、フレームワーク部分は pear を通じて管理されます。それでは、最初にこれら 2 つの概念を記録しましょう。興味がない場合は、このセクションをスキップしてください。
phar は PHP パッケージング ソリューションです。つまり、PHP プログラムまたは PHP Web サイトをパッケージ化して一緒に配布したり、機能モジュールとして呼び出すこともできます。したがって、phpunit はツールプログラムを phar に完全にパッケージ化することができます。phar を実行する場合、通常は php コマンドを使用する必要があります。
リーリー上記のコマンドを使用してphpunitの実行ファイルをダウンロードすると、pharパッケージであることがわかります。
pear は PHP 拡張ライブラリ システムです。これは、初期の頃は PHP を再利用することが困難であったためです。コンパイルされた言語は、構文がコンパクトで厳密であるため、再利用が容易です。 PHP は柔軟で変更可能であるため、再利用のための学習コストが比較的高いため、Pear は PHP 関数の再利用を実現するためのプログラミング仕様と配布システムを提案しました。現在、pear は Composer (後述) に置き換えられているようです。でも、古代のことは回り道を経てきたので、書き留めておいたほうがいいかもしれません。Mac では次のように pear をインストールできます:
リーリー
ご覧のとおり、go-pear も phar ですが、pear をインストールするための php スクリプトであり、php コマンドを使用して実行できます。インストールプロセス中に、php.ini ファイルを変更するかどうかを尋ねるメッセージが表示されます:リーリー
このヒントから次のことがわかります:pearの実行可能プログラムは/usr/bin/pearにインストールされます
Pear には、/usr/share/pear という作業ディレクトリがあります。この作業ディレクトリを php.ini に追加する必要があります。インストール プログラムに自動的に追加させると、次のようになります。
リーリー
他のファイルをインクルードする PHP の require 関数やその他の関数を使用する場合、PHP は実際には現在のディレクトリに加えて include_path を検索します。この構成は、pear を通じてインストールされたプログラム コードが作業ディレクトリに保存されることを示しており、デフォルトでは作業ディレクトリに System.php が存在するため、次のコードが機能します。
リーリー
composerを使用してインストール
元々、phpunit は pear を介してインストールできましたが、時代が変わり、composer が非常に普及したため、phpunit は pear を介したオリジナルのインストール方法を廃止しました。結局、composer を使うしかありませんでした (パッケージ管理ツールは 10 本の指では足りないほどたくさんあります。将来的には水平比較を行う機会があります)。
最初にコンポーザーをバイパス状態でインストールします:
リーリー
在项目的根目录下,创建一个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] 发现了这个脚本,它将执行这个脚本而不是采用默认的方式去重置该表.
ヒント: 特定の状態ファイルが多すぎると、テスト時間が大幅に長くなります。そのため、ルックアップ サービスとして使用されるテーブルは変更されないため、特定の状態ファイルは必要ありません。ステータスファイル