この記事は Symfony Advent Calendar 2014 の 16 日目の記事です。
みなさん Symfony2 で自動テストを書くとき、fixture はどうやって作っていますか。Symfony 公式ドキュメントで紹介されている DoctrineFixturesBundle を利用している方が多いかもしれませんね。
今回は fixture を簡単に作ることができる hautelook/AliceBundle を紹介したいと思います。この Bundle を導入すると fixture を yml で書けるようになるほか、様々な便利機能が利用できます。DoctrineFixturesBundle 単体の時と比べ fixture が格段に書きやすくなるので、Symfony を使うすべての人にオススメしたい Bundle です。
動作確認環境
- Symfony2.3
- Symfony2.6
目次
- AliceBundle とは
- AliceBundle で fixture はこう変わる
- とりあえずこれだけ知っておけば OK な機能
- 1. ひとつの yml に複数の Entity
- 2. ループ
- 3. ダミーデータ生成
- 4. 関数の利用
- 5. 一度登録した fixture を別の fixture で再利用する
- その他にも便利機能がいっぱい
- インストール方法
- コマンドラインとテストコードからの呼び出し方
AliceBundle とは
AliceBundle とは fixture 生成ツール nelmio/alice を Symfony の DoctrineFixureBundle で使えるようにする Bundle です。EntityManager 経由で作っていた fixture を yml 化できます。
また Alice は fzaninotto/Faker とも連動します。Faker はダミーデータを作成するライブラリで、人名や住所、電話番号などのダミーデータをランダムで生成します。国際化対応されていて、日本語のダミーデータも用意されています。
AliceBundle で fixture はこう変わる
AliceBundle の導入で fixture がどのように変わるか見ていきましょう。
導入前
まずは導入前。DoctrineFixturesBundle 標準の Fixture です。Entity の Setter でデータが埋もれてしまい、ゴチャっとした印象。数が増えてくるとしんどいです。
src/Acme/HelloBundle/DataFixtures/ORM/UserFixtureLoader.php
use Doctrine\Common\DataFixtures\AbstractFixture;
use Nelmio\Entity\User:
class UserFixtureLoader extends AbstractFixture
{
public function load(ObjectManager $manager)
{
$user1 = new User();
$user1->setUsername('hoge');
$user1->setFullname('ほげ');
$user1->setBirthDate(new DateTime('2014-12-16'));
$user1->setEmail('hoge@example.com');
$user1->setNumber(20);
$manager->persist($user1);
//...
//以降fixtureが続く
//...
$manager->flush();
$this->addReference('user1', $user1);
//...
}
}
導入後
fixture のデータ部分が yml で置き換わり、スッキリしました。
src/Acme/HelloBundle/DataFixtures/ORM/UserFixtureLoader.php
namespace Acme\HelloBundle\DataFixtures\ORM;
use Hautelook\AliceBundle\Alice\DataFixtureLoader;
class UserFixtureLoader extends DataFixtureLoader
{
protected function getFixtures()
{
return array(
__DIR__ . '/user.yml',
);
}
}
src/Acme/HelloBundle/DataFixtures/ORM/user.yml
Nelmio\Entity\User:
user1:
username: 'hoge'
fullname: 'ほげ'
birthDate: <dateTime('2014-12-16')>
email: 'hoge@example.com'
number: 20
# ...
# 以降 fixture が続く
# ...
とりあえずこれだけ知っておけば OK な機能
いろんな機能がありますが、とりあえずこれだけ知っておけば OK な機能を紹介します。
- ひとつの yml に複数の Entity
- ループ
- ダミーデータ生成
- 関数の利用
- 一度登録した fixture を他の fixture から呼び出す
順番にみていきましょう。
1. ひとつの yml に複数の Entity
ひとつの yml には複数の Entity を書くことができます。Entity ごとに yml を分けるかどうかは好みですが、foreign key 制約のあるテーブルにデータを投入するときは同じ yml に書く必要があります。データ投入時だけ一時的に foreign key 制約をオフにしてしまってもいいです。
Nelmio\Entity\User:
user1:
username: 'admin'
fullname: '管理者'
Nelmio\Entity\Group:
group1:
name: '管理者グループ'
参考
alice/README.md#creating-fixtures
2. ループ
fixture のループができます。短いコードで複数のデータが作れます。大量データが欲しい場合は Loader クラスで ini_set('memory_limit', '1024M');
のようにしてメモリ上限を増やすといいです。
for
風ループ
Nelmio\Entity\User:
user{1..10}:
username: suzuki<current()>
foreach
風ループ
Nelmio\Entity\User:
user{suzuki, sato}:
username: <current()>
<current()>
は、ループのキーを返します。
参考
alice/README.md#fixture-ranges
3. ダミーデータ生成
Faker によるランダムなダミーデータの生成ができます。自動テストの fixture というよりは、受け入れ試験などでユーザが実際に入力したようなデータが欲しいときに役立ちます。ダミーデータは config.yml の設定で日本語になります。日本語化の設定は後述します。
Nelmio\Entity\User:
user{1..5}:
username: <username()>
fullname: <lastName()> <firstName()>
birthDate: <dateTime()>
email: <email()>
number: <numberBetween(1, 200)>
投入されるデータはこんな感じ。
username | fullname | birth_date | number | |
---|---|---|---|---|
akira72 | 三宅 千代 | 2001-07-20 | thirokawa@mail.goo.ne.jp | 200 |
xhirokawa | 中津川 加奈 | 1999-04-05 | isuzuki@suzuki.net | 68 |
rika.tanabe | 工藤 桃子 | 1984-05-17 | naoko.tanabe@hotmail.co.jp | 105 |
tanabe.rika | 杉山 千代 | 1984-12-09 | akira69@tanabe.jp | 183 |
naoko.tanabe | 山田 里佳 | 2005-10-15 | uno.hiroshi@yoshimoto.info | 46 |
利用できる関数は fzaninotto/Faker#formatters を参照。中でも便利なのが 日付関数 で、前日や1ヶ月後などの日付を簡単に作成できます。
yml で fixture を書けるので忘れがちですが、各フィールドでは Entity の型を適切に選択しましょう。例えば DateTime 型のフィールドに date('now')
の 関数を使うと型変換エラーになります。
4. 関数の利用
Faker により提供される関数以外にも、自分で定義した関数を呼び出すこともできます。以下の例では、グループ名をランダムで返す groupName()
メソッドを利用しています。
class UserFixtureLoader extends DataFixtureLoader
{
protected function getFixtures()
{
return array(
__DIR__ . '/group.yml',
);
}
public function groupName()
{
$names = array(
'Group A',
'Group B',
'Group C',
);
return $names[array_rand($names)];
}
}
src/Acme/HelloBundle/DataFixtures/ORM/group.yml
Nelmio\Entity\Group:
group1:
name: <groupName()>
よく使うテスト用の共通メソッドは親クラスや trait などに切り出すのが良いと思います。
参考
alice/README.md #custom-faker-data-providers
5. 一度登録した fixture を別の fixture で再利用する
一度登録した fixture にラベルをつけ、一度登録した Fixture を @user1
のようにして他の fixture から参照することができます。ラベルにはループのキーも使えます。
Nelmio\Entity\User:
user{1..3}:
username: 管理者<current()>
Nelmio\Entity\Group:
group1:
name: '管理者グループ'
user: @user1
user_name: @user1->name
fixture の参照をうまく使い、同じようなテストデータを繰り返し作ってしまうことはなるべく避けたいです。
その他にも便利機能がいっぱい
紹介した以外にもいろんな機能があります。詳しくはマニュアルをご確認ください。
インストール方法
Composer からインストールします。
$ composer.phar require "doctrine/data-fixtures dev-master"
$ composer.phar require "doctrine/doctrine-fixtures-bundle 2.2.*"
$ composer.phar require "hautelook/alice-bundle 0.1.*"
AppKernel.php
に Bundle を追加します。
app/AppKernel.php
public function registerBundles()
{
$bundles = array(
// ...
new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(),
new Hautelook\AliceBundle\HautelookAliceBundle(),
// ...
);
}
config.yml
に設定を追加します。
app/config/config.yml
hautelook_alice:
locale: ja_JP
詳細は hautelook/AliceBundle を参照してください。
コマンドラインとテストコードからの呼び出し方
fixture の読み込み方法は、DoctrineFixturesBundle と同じです。そういえば 2 年前に DoctrineDataFixtures の使い方 を本ブログで紹介しました。
コマンドラインからの呼び出し方
$ php app/console doctrine:fixtures:load --fixtures=src/Acme/HelloBundle/DataFixtures/ORM/
テストコードからの呼び出し方
まず fixture を Load するメソッドを親クラスに作ります。trait でもいいですよ。
<?php
namespace Acme\HelloBundle\Tests;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Acme/HelloBundle/DataFixtures/ORM/UserFixtureLoader;
/**
* Fixtureをテストで使う場合はこのクラスを継承してください
*/
class FixtureAwareWebTestCase extends WebTestCase
{
/**
* 指定された fixture を読み込みます
*
* @param FixtureInterface[] $fixtures
*/
public function loadFixtures($fixtures)
{
//boot kernel
$kernel = $this->createKernel();
$kernel->boot();
//load data fixtures
$loader = new ContainerAwareLoader($kernel->getContainer());
foreach ($fixtures as $fixture) {
$loader->addFixture($fixture);
}
//execute query with truncate table
$manager = $kernel->getContainer()->get('doctrine')->getManager();
$purger = new ORMPurger($manager);
$purger->setPurgeMode(ORMPurger::PURGE_MODE_TRUNCATE);
$executor = new ORMExecutor($manager, $purger);
$executor->execute($loader->getFixtures());
}
}
各テストクラスの setUp
などで親クラスの loadFixtures()
メソッドを呼び出します。
namespace Acme\HelloBundle\Tests\Controller;
use Acme\HelloBundle\DataFixtures\ORM\UserFixtureLoader;
use Acme\HelloBundle\Tests\FixtureAwareWebTestCase;
class HelloControllerTest extends FixtureAwareWebTestCase
{
public function setUp()
{
parent::setUp();
$this->loadFixtures(
array(
new UserFixtureLoader(),
)
);
}
public function testHoge()
{
// your test
}
}
まとめ
- fixture が yml でスッキリ
- DoctrineFixturesBundle の機能はそのまま利用できる
- Faker を使ったダミーデータの生成は、「本番っぽい」データが大量に欲しい時に便利
- 自分で定義した関数も利用できる
- 一度登録した fixture は別の fixture から再利用できる
プログラム本体と同様に、fixture のメンテナンスしやすいことは大事だと思います。fixture が書きづらいとみんなテストを書かなくなってしまうんですよね。「fixture めんどい」「書く時間がない」「あとでやる」と言って。fixture はあなどれません。
AliceBundle で fixture を作る手間を減らし、どんどんテストを書きましょう。