Issei.M's Techlog

Web/iOS エンジニアの僕が技術関連のメモ等をつらつらと。主に Symfony について書いています。

[Symfony] LiipFunctionalTestBundleを使ってファンクショナルテストで楽をする

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

いよいよAdvent Calendarも最終週、残す所あと5日となりましたね。

LiipFunctionalTestBundleは、以前日本Symfonyユーザ会でも紹介されていたSymfonyアプリケーションのファンクショナルテストの作成を補助するバンドルで、これを使うとファンクショナルテストを少し楽に書く事ができます。

今回はこのバンドルを使ったファンクショナルテストの書き方の例を紹介します。

インストール

Composerでインストールします。テスト用のライブラリなので、require-dev に追記しましょう:

"require-dev": {
    "liip/functional-test-bundle": "dev-master"
}
php ./composer.phar update liip/functional-test-bundle

Kernelにバンドルを追加します。通常は、test 環境のみでOKだと思います:

<?php # app/AppKernel.php

// ...

class AppKernel extends Kernel
{
    public function registerBundles()
    {
        // ...
        
        if ('test' === $this->getEnvironment()) {
            $bundles[] = new Liip\FunctionalTestBundle\LiipFunctionalTestBundle();
        }
    }
    
    // ...
}

最後に test 環境の設定を一部変更します:

# application/config/config_test.yml

framework:
    test: ~
    session:
        storage_id: session.storage.filesystem

必要な設定はこれだけですが、テスト用のDBエンジンにSQLiteを使いたい場合は、以下の行を追加します:

doctrine:
    dbal:
        default_connection: default
        connections:
            default:
                driver: pdo_sqlite
                path:   %kernel.cache_dir%/test.db
                
liip_functional_test:
    cache_sqlite_db: true

こうする事でテスト用のDBエンジンにSQLiteが使用され、テストで使用するフィクスチャを初期化するのが非常に楽になります。実際に使用するDBエンジンと互換性があれば、この設定は非常に便利です。

これで準備はOKです。

テストクラスの作成

通常、Symfonyのファンクショナルテストを書く時はFrameworkBundleのWebTestCaseを継承しますが、今回はLiipFunctionalTestBundleのWebTestCaseを継承します。

<?php // src/AppBundle/Tests/Controller/DefaultControllerTest.php

namespace AppBundleests\Controller;

use Liip\FunctionalTestBundleest\WebTestCase;

class DefaultControllerTest extends WebTestCase
{
}

実際はこのWebTestCaseは、FrameworkBundleのWebTestCaseを継承しているので使い勝手はさほど変わりません。 代わりにいくつかのショートカット機能を備えています。

フィクスチャの読み込み

多くのファンクショナルテストがテスト用のフィクスチャを読み込む必要がありますが、LiipFuncionalTestBundleではそれが非常に簡単に行えます。::loadFixtures() を実行するだけです。

※予めプロジェクトにDoctrineFixturesBundleをインストールしておく必要があります。

<?php // src/AppBundle/Tests/Controller/DefaultControllerTest.php

// ...

class DefaultControllerTest extends WebTestCase
{
    $this->loadFixtures([
        'AppBundle\DataFixtures\ORM\LoadUserData',
    ]);
}

引数には、配列で読み込みたいフィクスチャのクラスをFQCNで定義します。この時、先ほど設定でDBエンジンをSQLiteにしていれば、自動的にスキーマを初期化した上で読み込んでくれます。
また、勿論フィクスチャは複数読み込むことができ、空の配列を指定した場合はスキーマの初期化だけが行われます(=空のテーブルだけが作成されます)

もしSQLiteを使っていなければ、フィクスチャを読み込む前に以下を実行する必要があります:

<?php

$em = $this->getContainer()->get('doctrine')->getManager();
if (!isset($metadatas)) {
    $metadatas = $em->getMetadataFactory()->getAllMetadata();
}
$schemaTool = new SchemaTool($em);
$schemaTool->dropDatabase();
if (!empty($metadatas)) {
    $schemaTool->createSchema($metadatas);
}
$this->postFixtureSetup();

// ↑フィクスチャ読み込みの前に手動でスキーマを初期化

$this->loadFixtures([
    'AppBundle\DataFixtures\ORM\LoadUserData',
]);

何気に面倒ですが、スーパークラスやトレイトに定義しておけば問題ないと思います。

認証済みのクライアント作成

ファンクショナルテストで各種ページのテストを行う時、::createClient() で仮想クライアントを作成してリクエストを送信しますが、ページがユーザの認証を必要としている場合、少々面倒ですよね。

この為、LiipFunctionalTestBundleでは予めユーザを認証させた状態のクライアントを作成することができます。 ::loginAs() に認証させたいユーザオブジェクトと、ファイヤウォール名を指定するだけです:

<?php

$user = $this->getContainer()->get('doctrine')->getRepository('AppBundle:User')->find(1);
$this->loginAs($user, 'customer_area');

$client = $this->makeClient();

この時、クライアントの作成には ::makeClient() を使用する必要がある点だけ注意して下さい。
また、ファイヤウォール名とは、security.ymlfirewalls ディレクティブに指定している各ファイヤウォールの名称の事です:

# app/config/security.yml

security:
    # ...
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
               
        customer_area: # ←これの事
            pattern: # ...
            # ...

勿論、一度に複数のファイヤウォールの認証をパスする事もできます:

<?php

$user = $this->getContainer()->get('doctrine')->getRepository('AppBundle:User')->find(1);
$adminUser = $this->getContainer()->get('doctrine')->getRepository('AppBundle:User')->find(2);

$this->loginAs($user, 'customer_area');
$this->loginAs($adminUser, 'administration_area');

$client = $this->makeClient();

コンソールコマンドの実行

コンソールコマンドの実行も非常に簡単です。::runCommand() を実行するだけでOK:

<?php // src/AppBundle/Tests/Command/SayHelloCommandTest.php

// ...

class SayHelloCommandTest extends WebTestCase
{
    $output = $this->runCommand('app:say:hello', [ '--dry-run' => false, ]);
}

第1引数にコマンド名、第2引数にパラメータを指定します。また、返り値には標準出力に表示される結果がそのまま文字列で返されます。

その他に

HTML5のバリデーションや、DIコンテナのサービスIDから該当クラスのモックを作ったり、色々便利な機能が満載です。 これから、Symfonyでファンクショナルテストを作る方は是非試してみて下さい。