[Symfony] AliceBundleで自動テストのfixtureをyml化しよう

2014年12月16日

Symfony2 AliceBundle

この記事は Symfony Advent Calendar 2014 の 16 日目の記事です。

みなさん Symfony2 で自動テストを書くとき、fixture はどうやって作っていますか。Symfony 公式ドキュメントで紹介されている DoctrineFixturesBundle を利用している方が多いかもしれませんね。

今回は fixture を簡単に作ることができる hautelook/AliceBundle を紹介したいと思います。この Bundle を導入すると fixture を yml で書けるようになるほか、様々な便利機能が利用できます。DoctrineFixturesBundle 単体の時と比べ fixture が格段に書きやすくなるので、Symfony を使うすべての人にオススメしたい Bundle です。

動作確認環境

  • Symfony2.3
  • Symfony2.6

目次

  1. AliceBundle とは
  2. AliceBundle で fixture はこう変わる
  3. とりあえずこれだけ知っておけば OK な機能
  4. 1. ひとつの yml に複数の Entity
  5. 2. ループ
  6. 3. ダミーデータ生成
  7. 4. 関数の利用
  8. 5. 一度登録した fixture を別の fixture で再利用する
  9. その他にも便利機能がいっぱい
  10. インストール方法
  11. コマンドラインとテストコードからの呼び出し方

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 な機能を紹介します。

  1. ひとつの yml に複数の Entity
  2. ループ
  3. ダミーデータ生成
  4. 関数の利用
  5. 一度登録した 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)>

投入されるデータはこんな感じ。

usernamefullnamebirth_dateemailnumber
akira72三宅 千代2001-07-20thirokawa@mail.goo.ne.jp200
xhirokawa中津川 加奈1999-04-05isuzuki@suzuki.net68
rika.tanabe工藤 桃子1984-05-17naoko.tanabe@hotmail.co.jp105
tanabe.rika杉山 千代1984-12-09akira69@tanabe.jp183
naoko.tanabe山田 里佳2005-10-15uno.hiroshi@yoshimoto.info46

利用できる関数は fzaninotto/Faker#formatters を参照。中でも便利なのが 日付関数 で、前日や1ヶ月後などの日付を簡単に作成できます。

参考
alice/README.md#faker-data

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 の参照をうまく使い、同じようなテストデータを繰り返し作ってしまうことはなるべく避けたいです。

参考
alice/README.md#references

その他にも便利機能がいっぱい

紹介した以外にもいろんな機能があります。詳しくはマニュアルをご確認ください。

インストール方法

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 を作る手間を減らし、どんどんテストを書きましょう。

-技術ブログ
-