TRAJOIN is an Application to Translate symfony documents Jointly.
home > 1.2/cookbook/en > model_unit_testing.txt
PropelもしくはDoctrineモデルのための単体テストを書くのは極めて簡単です。 このチュートリアルでは、モデルのためのベターなテストを書くための重要なティップスとベストプラクティスを学びます。
Propelのモデルクラスをテストするには、データベースが必要です。開発のために利利用しているものは既にありますが、 テストのために常に専用のものを作っておくことは良い習慣です。
すべてのテストはtest環境の元で実行されるので、config/databases.yml設定ファイルを編集してtest環境のためのデフォルトの設定を上書きする必要があるだけです:
test:
propel:
param:
dsn: mysql:dbname=myproject_test;host=localhost
dev:
# dev configuration
all:
propel:
class: sfPropelDatabase
param:
dsn: mysql:dbname=myproject;host=localhost
username: someuser
password: somepa$$word
encoding: utf8
persistent: true
pooling: true
classname: PropelPDO
このケースにおいて、データベースの名前だけを変更しましたが、 データベースのエンジンを変更して例えばSQLiteを利用することもできます。
データベースを設定したので、propel:insert-sqlタスクを利用することでテーブルを作ることができます:
$ php symfony propel:insert-sql --env=test
テストのための専用データベースがあるので、単体テストを立ち上げるたびにテストデータ(フィクスチャ)をロードする方法が必要です。 これはテストを実行するたびにデータベースを同じ状態にしたいからです。
sfDataクラスのおかげでこれを実現する方法はとても簡単です:
$loader = new sfPropelData();
$loader->loadData(sfConfig::get('sf_test_dir').'/fixtures');
loadData()メソッドはディレクトリもしくはファイルを1番目の引数として取ります。
共通のfixturesディレクトリは次の通りです:
test/
fixtures/
10_categories.yml
20_articles.yml
30_comments.yml
すべてのファイルの接頭辞として数字が追加されていることに注目して下さい。 これはデータのロードの順番をコントロールするための簡単な方法です。プロジェクトにおいて後で、フィクスチャデータを差し込む必要がある場合、 既存の数字の間に好きな数字を入れることで簡単にできます:
test/
fixtures/
10_categories.yml
15_must_be_laoded_between_categories_and_articles.yml
20_articles.yml
30_comments.yml
明敏な読者はsymfony bookではこれらのフィクスチャをdata/ディレクトリに設置することを推奨されているのに対して、筆者がフィクスチャをtest/ディレクトリに設置したことに注目するでしょう。これは本当に好みの問題ですが、筆者はフィクスチャをこれらの2つのディレクトリの中で編成することを好みます。フィクスチャを2つの異なるグループに分類できるからです:
data/fixtures: アプリケーションを実際に稼働させるために必要なすべての初期データを含むtest/fixtures: (単体と機能)テストによって必要なすべてのデータを含むテストデータの一式が小さいときはこのシンプルなスキーマは立派に機能しますが、モデルが成長し、もっとたくさんのフィクスチャを持つようになったら、データベースの中でそれらをロードする時間が重大になる可能性があります。 ですので、テストデータのサブセットだけをロードする方法が必要です。これを行う1つの方法は主要な機能ごとにサブディレクトリを作ることでテストデータの下位分類を行うことです:
test/
fixtures/
10_cms/
10_categories.yml
20_articles.yml
30_comments.yml
20_forum/
10_threads.yml
これでメインのfixturesディレクトリをロードする代わりに、テストしたいモデルクラスに対応したサブディレクトリの1つだけをロードできます。
しかし大抵の場合、ユーザーのように、共有データをロードすることも必要です:
test/
fixtures/
00_common/
10_users.yml
10_cms/
10_categories.yml
20_articles.yml
30_comments.yml
20_forum/
10_threads.yml
このユースケースを簡単にするために、loadData()メソッドはディレクトリかつ/もしくはファイルの配列を取ることができます:
// ユーザーとCMSのすべてのデータをロードする
$loader = new sfPropelData();
$loader->loadData(array(
sfConfig::get('sf_test_dir').'/fixtures/00_common/10_users.yml',
sfConfig::get('sf_test_dir').'/fixtures/10_cms',
));
このコードによって10_users.ymlフィクスチャファイルはロードされたすべてのフィクスチャファイルは10_cmsディレクトリの中で見つかります。
専用データベースとデータベースを既知の状態にする方法があるので、Articleモデルのための単体テストを作りましょう。
ファイルをブートストラップするPropelの典型的な単体テストは次の通りです:
// test/unit/model/ArticlePeerTest.php
include(dirname(__FILE__).'/../../bootstrap/unit.php');
$configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'test', true);
new sfDatabaseManager($configuration);
$loader = new sfPropelData();
$loader->loadData(sfConfig::get('sf_test_dir').'/fixtures');
$t = new lime_test(1, new lime_output_color());
$t->diag('::retrieveBySlug()');
$article = ArticlePeer::retrieveBySlug('the-best-framework-ever');
$t->is($article->getTitle(), 'The Best Framework Ever', '->retrieveBySlug()は特定のスラグにマッチする記事を返す');
このスクリプトの内容は一目瞭然です:
include(dirname(__FILE__).'/../../bootstrap/unit.php');
test環境のための設定オブジェクトを作りデバッグ機能を有効にします:
$configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'test', true);
これによってPropelのクラスのオートローディングも初期化されます。
データベースマネージャを作ります。これはdatabases.yml設定ファイルをロードすることでPropelの接続を初期化します:
new sfDatabaseManager($configuration);
sfPropelDataを利用することでテストデータをロードします:
$loader = new sfPropelData();
$loader->loadData(sfConfig::get('sf_test_dir').'/fixtures');
単体テストを書くことに慣れていないのであれば、最初は怖いかもしれません。
筆者がテストする必要があることを知るためにいつも使ういくつかのティップスは次の通りです:
筆者のテストファイルは同じパターンで常に構造化されます:
// テストするメソッドでメッセージを出力する(->はインスタンスメソッドに対して、::はクラスメソッドに対して)
$t->diag('->methodName()');
// 1回につきシンプルなセンテンスで表現できる1つのことをテストする
// センテンスは常にメソッドの名前で始まる
// 動詞はしなければならないこと、どのように振る舞わなければならないことなどを表現する
$t->is($object->methodName(), 1, '->methodName()は引数が渡されていない場合は1を返す');
テストを書くとき、複雑なコードの条件をテストすることは忘れがちです。
symfony 1.2はtest:coverageというコードカバレージをテストするための小型で手軽なタスクを搭載しています。
ですので、筆者は特定のクラスに対してテストを書いた後に、すべてのテストを実行したことを確認するために常にtest:coverageタスクを起動させます:
$ php symfony test:coverage test/unit/model/ArticleTest.php lib/model/Article.php
最初の引数はテスト用のファイルもしくはディレクトリです。 2番目の引数はコードカバレージを知りたいファイルもしくはディレクトリです。
どの行がカバーされていないのか知りたいのであれば、--detailedオプションを追加します:
$ php symfony test:coverage --detailed test/unit/model/ArticleTest.php lib/model/Article.php