データベースを含んだ自動テストは面倒ですね。なにが面倒って、テスト用のデータ、いわゆるフィクスチャを準備するのがもう。
Yii はデータベースの結果をモックする純粋なロジックテストではなく、ファンクショナルテストやデータベース込みのテストを指向しています。2.0 になって、BDDふうのテスティングフレームワーク Codeception のテストプロジェクトが一式ついてくるようになりました。BDD ふうにページをテストするにはホンモノが動くことが前提、つまりデータベースも当然要るわけです。
フィクスチャはすべて手書きしてもいいのですが、たとえば 21 件以上でページネーションすることを試そうと思ったら...
というわけで Faker を活用しましょう。
Yii のプロジェクトテンプレートの tests
フォルダ以下には、テスト環境専用の codeception\bin\yii
コマンドが置いてあり、こんな感じで、フィクスチャのサポートが追加で組み込まれています。
$config = yii\helpers\ArrayHelper::merge(
// ... ,
[
'controllerMap' => [
'fixture' => [
'class' => 'yii\faker\FixtureController',
'fixtureDataPath' => '@tests/codeception/fixtures/data',
'templatePath' => '@tests/codeception/templates',
'namespace' => 'tests\codeception\fixtures',
],
],
]
);
設定にしたがって tests/codeception/templates
にフィクスチャのテンプレートを書きます。たとえばユーザー用のフィクスチャなら templates/user.php
にこう書きます。
<?php
/**
* @var $faker \Faker\Generator
* @var $index integer
*/
return [
'email' => $faker->email,
'password_hash' => Yii::$app->security->generatePasswordHash('password_' . $index),
'auth_key' => Yii::$app->security->generateRandomString(),
'name' => $faker->firstName . ' ' . $faker->lastName,
'birthday' => $faker->date(),
'created_at' => $faker->unixTime,
'updated_at' => $faker->unixTime,
];
$faker
からプロパティを取ると、取り出すたびにデタラメなそれらしいデータが出てきます。ランダムだと困るものは、 $index
を使うと計算で出したり決まった配列から取り出したりできます。
で、 tests/codeception/bin/yii fixture/generate user
を実行するとテンプレートのパスとデータのパスが設定してあるので...
$ tests/codeception/bin/yii fixture/generate user
Fixtures will be generated under the path:
/.../tests/codeception/fixtures/data
Templates will be taken from path:
/.../tests/codeception/templates
* user
Generate above fixtures? (yes|no) [no]: yes
The following fixtures template files were generated:
* user
はい、これでできました。じゃーん!
<?php
return [
[
'email' => 'pauline22@weber.info',
'password_hash' => '$2y$13$kU/MfgI/XUD6JacW7VFe2O3iab3pksTEvrXaJ9c0mof86lnNEJwW.',
'auth_key' => 'l7iv336Q00OrP0Mcn7sUgvbUAhd3gU1W',
'name' => 'Ophelia Jacobs',
'birthday' => '1980-09-09',
'created_at' => 602074437,
'updated_at' => 390537715,
],
[
'email' => 'kacie78@yahoo.com',
'password_hash' => '$2y$13$ry7b0lKIcY1grUQmQBkjgupbRsNxAnoOsAgOwJsxgXQCnlljYM3W6',
'auth_key' => 'Ux5iYaPTpY5pgym9fuwEjrZXOCdZbXO5',
'name' => 'Audra Cole',
'birthday' => '2010-05-17',
'created_at' => 447193310,
'updated_at' => 293260057,
],
];
2個じゃ足りないなら、
tests/codeception/bin/yii fixture/generate user --count=10
で、こういうのが 10 件でも 20 件でも作れます。
本当にバラバラなデータを手で作るのは面倒だし、人間が手でやるとどうしても恣意的になってしまいます。思い込みでテストケースの漏れが出てくることもあります。
テンプレートから生成することのメリットとして、再現性のないパスワードハッシュを固定できるというのもあります。フィクスチャデータで generatePasswordHash
を呼ぶと、セキュリティの都合で実際には毎回違うデータになってしまいます。細かいことかもしれませんが、フィクスチャデータで値を生成すると、場合によっては失敗を再現できないかもしれません。フィクスチャデータはテストコードの一部です。
というわけで、テンプレートがあるからといって、フィクスチャデータをバージョン管理しないというのはナシです。テストがグリーンでコミットしたことを確実に示すために、フィクスチャはバージョン管理しましょう。
バージョン管理するということは、もうこのデータは開発者のものです。生成し直すまで、好きなように編集して、都合の悪いところは適当に書き換えましょう。多対多テーブルなんかは、テンプレートを書かずにフィクスチャを直接書いてもいいかもしれません。
フィクスチャデータを使うには、クラスを定義しなければなりません。といっても、AssetBundle
並みに非常に簡単な実装です。テスト用 yii
コマンドの FixtureController
の namespace
が指すところに...
<?php
namespace tests\codeception\fixtures;
use yii\test\ActiveFixture;
class UserFixture extends ActiveFixture
{
public $modelClass = 'app\models\User';
public $depends = [];
}
$modelClass
はどのテーブルにロードするかという情報を引くための ActiveRecord クラスです。
$depends
には、このフィクスチャをロードする前にロードして欲しい他のフィクスチャのクラス名を複数登録します。まさに AssetBundle
のノリですね。これで、ロード時に外部キーの先がないなんてこともありません。
これでデータをロードすることができるようになりました。自動テストを実行する前に、作ったデータが実際にデータベース入るかどうかを確認しておきます。テスト用のデータベースをマイグレーションして、そこにすべてのフィクスチャを流し込みます。
tests/codeception/bin/yii migrate/up
tests/codeception/bin/yii fixture/load "*"
成功したらOKです。外部キー制約や文字数制限などの都合で入らないフィクスチャを作っていると、テストでも使うことができません。確認し終わったらアンロードします。
tests/codeception/bin/yii fixture/unload "*"
さて、ここまでやってもらえればもうあとはモチベーションを下げるヤツはいません。自動テストをサクサク書いていきましょう。
時間もないので、各種のテストでどうやってロードするかは、マニュアルを参照したり、アプリケーションテンプレートから読み取ったりしてください。
なお、Faker は一般的な道具なので、Laravel でも Symfony でも使えます。Packagist には CakePHP3 のプラグインも見かけました。
Laravel で Faker を使う場合は Jeff の記事を参照して下さい。
メリー・クリスマス