symfonyフレームワークは常に機能テストフレームワークを搭載してきており、これは主要な強みの1つです。
機能テスト(functional test)とは何でしょうか?機能テストのゴールはすべてのアプリケーションレイヤーの統合: ルーティングからコントローラ、テンプレート、とデータベースのコールをテストすることです。 これらはユニットテストの置き換えにはなりません。
唯一簡単にテストできないものはテンプレートに埋め込まれたJavaScriptのコードです。 もちろんこのためにseleniumのようなツールを利用できます。 しかし良い知らせは機能テストはAjaxのような"何かの"JavaScriptコードをテストできることです。
仕事を行うために、機能テストフレームワークはブラウザをシミュレートします。 symfonyの内部とリクエストに基づいてレスポンスを生成する方法を知っているのでこれはWebサーバーを必要としません。 これによってそれぞれのリクエストの後でアプリケーションの状態のイントロスペクションを簡単で深く行うことができます。 もちろんレスポンスもしくはユーザーセッションのようなsymfonyコアオブジェクトだけでなく、モデルのようなあなた独自のコードもイントロスペクトできます。
symfonyがリリースされるごとに機能テストフレームワークはより改善されます。 今日は、symfony 1.2に追加されたすべてのよいところをお見せします。 驚く用意をしてください!
デカップリング
みなさんがご存じの通り、私はテストがとても好きです。
古いコードをよりベターなものにリファクタリングすることも好きです。
symfony 1.2に関しては、ブラウザ(sfBrowser
)とテストブラウザ(sfTestBrowser
)クラスをはるかに柔軟で設定しやすくするためにリファクタリングしました。
symfony 1.2に関しては、機能テストフレームワークは再利用可能で異なるいくつかのレイヤーで構成されています。
最も大きな変更はテスターの導入です。 テスター(tester)はアプリケーションの特定のレイヤーをテストする方法を知っているオブジェクトです。 リクエスト、レスポンス、ユーザー、ビューキャッシュ、フォームとPropelに関してsymfonyはいくつかの組み込みのテスターを備えています。
重要度の低い変更はsfTestFunctional
クラスの導入です。
このクラスはアプリケーションをテストしてすべての登録されたテスターを管理するためにsfBrowser
オブジェクトに依存します。
次は典型的な機能テストです:
$browser = new sfTestFunctional(new sfBrowser()); $browser-> get('/')-> // テストを行う ;
symfony 1.0と1.1の後方互換性を維持するには、新しく非推奨のsfTestBrowser
クラスをまだ利用できます:
$browser = new sfTestBrowser(); $browser-> get('/')-> // 何かテストを行う ;
テスター
ですので、実際にはすべてのテストはテスタークラスで行われます。 テスターはアプリケーションの特定の部分をテストする方法を知っています。
テスターはcheckResponseElement()
もしくはisRequestParameter()
のような、あなたが慣れ親しんだすべてのメソッドを置き換えます。
もちろん、後方互換性を維持するためにこれらのメソッドはまだ利用できます
(UPGRADE_TO_1_2
ファイルにはすべての古いメソッドとテスターの対応するメソッドの対照表があります)。
次のコードはリクエストテスターを利用することでisRequestParameter()
呼び出しを置き換える方法を示すシンプルな例です:
// symfony 1.2以前 $browser-> get('/')-> isRequestParameter('module', 'foo')-> checkResponseElement('h1', 'foo') ; // symfony 1.2の場合 $browser-> get('/')-> with('request')->isParameter('module', 'foo')-> checkResponseElement('h1', 'foo') ;
with('request')
呼び出しはすぐ次の呼び出しに対してリクエストテスターへの
流れるようなインターフェイスのコンテキストを切り替えます。
ですので、isParameter()
メソッドはsfTesterRequest
メソッドです。
コンテキストはテスターオブジェクトである呼び出しのブロックを作ることもできます:
$browser-> get('/')-> with('request')->begin()-> isParameter('module', 'foo')-> isParameter('action', 'index')-> end()-> checkResponseElement('h1', 'foo') ;
begin()
とend()
の間のすべてのメソッド呼び出しは現在のテスターオブジェクトに対して呼び出されます。
組み込みのテスタークラスによって提供されるテストメソッドを見てみましょう。
リクエストテスター
request
テスターはsfTesterRequest
クラスで定義され次のメソッドを持ちます:
メソッド | 説明 |
---|---|
isParameter |
リクエストパラメーターをテストする |
isMethod |
リクエストメソッドをテストする |
isFormat |
リクエストフォーマットをテストする |
hasCookie |
リクエストが渡されたCookieを持つかテストする |
isCookie |
Cookieの値をテストする |
$browser-> get('/')-> with('request')->begin()-> isParameter('module', 'foo')-> isMethod('get')-> isFormat('html')-> hasCookie('foo')-> isCookie('foo', 'bar')-> end() ;
レスポンステスター
response
テスターはsfTesterResponse
クラスで定義されており次のメソッドがあります:
メソッド | 説明 |
---|---|
isStatusCode |
レスポンスステータスコードをテストする |
contains |
単純な正規表現でレスポンスの内容をテストする |
isHeader |
与えられたヘッダーの値をテストする |
checkElement |
CSS3セレクターの値をチェックする |
$browser-> get('/')-> with('response')->begin()-> isStatusCode(200)-> contains('foo')-> isHeader('Content-Type', 'text/plain')-> checkElement('ul.foo li:last', '/foo/')-> end() ;
ビューキャッシュテスター
view cache
テスターはsfTesterViewCache
クラスで定義され次のメソッドを持ちます:
メソッド | 説明 |
---|---|
isCached |
ページ/アクションがキャッシュの中にあるかチェックする |
isUriCached |
特定のURI(パーシャルも可能)がキャッシュの中にあるかチェックする |
$browser-> get('/')-> with('view_cache')->begin()-> isCached(true)-> isUriCached('@sf_cache_partial?module=foo&action=_partial&sf_cache_key=some_cache_key')-> end() ;
ユーザーテスター
user
テスターはsfTesterUser
クラスで定義され次のメソッドを持ちます:
メソッド | 説明 |
---|---|
isCulture |
ユーザーのcultureをテストする |
isAuthenticated |
ユーザーが認証されたかチェックする |
hasCredential |
ユーザーのクレデンシャルをチェックする |
isAttribute |
与えられた属性の値をテストする |
isFlash |
flash変数の値をテストする |
$browser-> get('/')-> with('user')->begin()-> isCulture('fr')-> isAuthenticated(true)-> hasCredential('admin')-> isAttribute('sfguard_user_id', '3')-> isFlash('notice', '/foo/')-> end() ;
フォームテスター
新しくセクシーなテスターを発見する時間です!
form
テスターはsfTesterForm
クラスで定義されています。
これはフォームが以前のリクエストで使われたかどうか知っており、フォームオブジェクト自身への参照を持ち、イントロスペクトが許可されているのか知っています。
メソッド | 説明 |
---|---|
hasErrors |
投稿されたフォームがエラーを持つかチェックする |
isError |
与えられたフィールドに対するエラーの値をテストする |
hasGlobalError |
グローバルエラー以外はisError と同じ |
isError()
メソッドはcheckResponseElement()
メソッドと同じ種類の2番目の引数を取ります。
$browser-> click('save', array(...))-> with('form')->begin()-> hasErrors()-> hasGlobalError('The login and password does not match.')-> isError('name', 'Required.')-> isError('name', '/Required/')-> isError('name', '!/Invalid/')-> isError('name')-> isError('name', false)-> isError('name', 1)-> end() ;
Propelテスター
propel
テスターは別の偉大なテスターです。
HTMLレスポンスチェックを置き換えませんが、テストすることが重要であるにもかかわらずブラウザに表示されないものをチェックするための手段です(たとえばユーザー用のlast_connection
タイムスタンプが更新されてきたかどうか、もしくは記事用のビューの番号が増分されてきたかどうか)。
propel
テスターはPropelプラグインのsfTesterPropel
で定義され使う前に登録しなければなりません:
$browser->setTester('propel', 'sfTesterPropel');
テスターが登録された後で、これをテストの中で利用できます:
$browser-> post('/')-> with('propel')->begin()-> check('Article', array('title' => 'foo'), false)-> check('Article', array('title' => '!foo'), false)-> check('Article', array(), 4)-> check('Article', array('title' => '%foo%'), true)-> check('Article', array('title' => '!%foo%'))-> check('Article', $criteria)-> end() ;
propel
テスターは1つのメソッド: check()
だけしか提供しません。
このメソッドはあなたが渡した引数に基づいて異なるふるまいをします:
- 最初の引数はモデルクラス名
- 2番目の引数は
Criteria
オブジェクトもしくは条件のシンプルな配列 - 3番目の引数は次の通り:
- オブジェクトが条件にマッチする場合は
true
- オブジェクトが条件にマッチしない場合は
false
- マッチするオブジェクトの数をチェックするために整数
- オブジェクトが条件にマッチする場合は
テスターを拡張もしくは作成する
テスターにはいくつかの利点があります:
- 分離: テスターのデカップリングのおかげで、以前よりもはるかに多くのテストメソッドを提供できます。
- 可読性: ブロックの概念と短いメソッドの名前のおかげで、テストはいっそう読みやすいです。
- 拡張性: それぞれのテスターをあなた独自のメソッドで拡張できるもしくは独自のテスタークラスを作成できます。
組み込みのテスターを拡張する
既存のテスターにメソッドを追加したい場合、組み込みのテスターを継承するクラスを作り独自クラスの名前で再登録する必要があります:
class ApplicationTesterRequest extends sfTesterRequest { // テスターメソッドを追加する } // 機能テストの中で $browser->setTester('request', 'ApplicationTesterRequest');
組み込みテスターのまとまりをオーバーライドする必要がある場合、setTesters
メソッドが使えます:
$browser->setTesters(array( 'request' => 'ApplicationTesterRequest', 'response' => 'ApplicationTesterResponse', ));
テスターメソッドは望むことは何でもできますが流れるインターフェイスを正しく動作させるためには常に次のコードで終わらなければなりません:
return $this->getObjectToReturn();
メソッドの中では、いくつかのオブジェクトにアクセスする権限があります:
$this->browser
: 現在のブラウザーメソッド$this->tester
:lime_test
オブジェクト
新しいテスターを作成する
ユニークな名前で登録することで新しいテスタークラスも登録できます:
$browser->setTester('my_tester', 'myTester');
テスタークラスはsfTester
を継承し次のメソッドを実装しなければなりません:
initialize()
: テストでwith()
を使うたびにこのメソッドが呼び出されます。 これはリクエストが送信された後でオブジェクトを取得するのに便利です:public function initialize() { $this->request = $this->browser->getRequest(); }
prepare()
: ブラウザオブジェクトへのコールの直前にこのメソッドが呼び出されます。 これはリクエストが送信される直前に何かを行う必要がある場合に便利です。
流れるようなインターフェイスにする
与えられたモジュールに対してたくさんの機能テストを書くとき、何が行われているのか視覚的な情報があると便利であることがあります。 新しいテスターは新しいレベルのインデントを追加するのでテストは読みやすくなります。
また、テストの分類を手助けするためにテキストを出力する新しいinfo()
メソッドがあります:
$browser-> info('First scenario: Form with errors')-> // ... 何かのテスト info('Second scenario: Valid form submission')-> // ... さらに何かのテスト ;
テストをデバッグする
機能テストで問題が起きるとき、ブラウザーに転送されるHTMLは原因の診断の手助けをしてくれます。 symfony 1.2に関しては、流れるようなインターフェイスのスタイルを乱すことなく、生成されたHTMLを表示するのはとても簡単です:
$browser-> get('/a_uri_with_an_error')-> with('response')->debug()-> // 実行されないテスト ;
debug()
メソッドはレスポンスヘッダーと内容を出力しブラウザーの流れを中断します。
同じdebug()
メソッドがform
テスト用にも存在し投稿された値と存在するのであればフォームエラーを出力します:
$browser-> post('/post_to_a_form_with_some_errors')-> with('form')->debug()-> // 実行されないテスト ;
今日はこれでお終いです。symfony製のアプリケーションをテストすることが簡単になったわけではありません。 ですので、新しいテストフレームワークが難しくなく時間を節約することをあなたが理解してくださることを期待しております。
新しいWebデバッグツールバーのパネルに関しては、新しいテスターを作成するのであれば、これらをプラグインパッケージとして作成することをためらわないでください。
This work is licensed under the Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License license.