Symfony Advent Calendar 2016 19日目の記事です。

はじめに

Symfony2でアプリケーションを開発している人たちの多くは、アプリケーションのファクショナルテストを書いているかと思いますが、 今回はアプリケーションのファンクショナルテストではなくて、バンドルのファンクショナルテストについて書きたいと思います。

バンドル単体のファンクショナルテストがない場合の問題

バンドルはKernelにインストールされて使用されます。
バンドルをファンクショナルテストしないままリリースしてしまうと、アプリケーションのKernelにインストールされてから問題に直面する可能性が上がってしまいます。

  • DIコンテナのコンパイルに失敗する
  • Deprecation Warningが出てしまう
  • 期待した通りに動かない
  • などなど・・・

問題は修正してしまえば良いのですが、Symfonyの文化圏内だとそう簡単には後方互換を捨てられません。
後方互換を維持したまま修正できれば何の問題もないです。もしそうでない場合はメジャーバージョンを上げるべきでしょうが、そうホイホイ上げたくありませんよね。

問題1: DIコンテナのコンパイルに失敗する

OSSで提供されているバンドルの多くは、Symfonyの複数のバージョンをサポートしています。
Symfonyはv2.3から後方互換性を維持してくれている ので、そこまで神経質にならなくても大丈夫ですが、メジャーバージョンを跨いで複数のSymfonyをサポートしていると、バンドルをアプリケーションのKernelにインストールした結果、DIコンテナのコンパイルに失敗するようになってしまう可能性は低くありません。

メジャーバージョンが変更される際に、削除が予定されているサービスは削除されるので、もし参照が残っていたりするとこれに該当します。

問題2: Deprecation Warningが出てしまう

Symfonyのアップグレードをスムーズに行わせる仕組みの1つにDeprecation Warningを出力する仕組みがあります。さらに、テスト結果からDeprecation Warningを確認できる仕組みも提供されています。

なのでSymfonyユーザーはDeprecation Warningに敏感です。
あるバージョンのSymfonyでバンドルを利用しているユーザーは大丈夫だけれども、より新しいバージョンのSymfonyを利用しているユーザーだとそうではない場合があります。

あるバージョンで非推奨となったAPIや、サービスを実行したり参照したりするとこれに該当します。

問題3: 期待した通りに動かない

Symfonyはプラガブルにフレームワークの振る舞いを変更する事ができ、もちろんアプリケーションだけでなくバンドルからも変更する事が可能ですが、それらはバンドル自身の機能ではありません。
例えばEventDispatcherはSymfonyのコアに組み込まれていますが、サービスとしてはFrameworkBundleが提供していますし、KernelのイベントもHttpコンポーネントが発行しています。

つまり、フレームワークの振る舞いを変更する機能がバンドル内にある場合、ユニットテストだけでは想定通りのタイミングで動作するのか、想定通りな値を受け取れるのか、想定通りに振る舞いを変更できているのか、そもそも動作するのか、何も保証できないという事です。アプリケーションの場合と同じですね。

どうやってファンクショナルテストするか

手順は基本的にアプリケーションの時と同じです。

  • テスト用のKernelを作る
  • テスト用のKernelにバンドルをインストールする
  • テスト用の設定をテスト用のKernelにロードさせる
  • テスト用のKernelでファンクショナルテストする

テスト用のKernelを作る

普段のアプリケーションと同じように、Symfony\Component\HttpKernel\Kernelを継承したKernelを作ります。

<?php

use Symfony\Component\HttpKernel\Kernel;

class TestKernel extends Kernel
{
}

テスト用のKernelにバンドルをインストールする

テストしたいバンドルと、普段のアプリケーションと同じようにFrameworkBundleもインストールします。

<?php

use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\HttpKernel\Kernel;

class TestKernel extends Kernel
{
+    public function registerBundles()
+    {
+        return [
+            new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
+            new Your\Bundle\YourSpecialBundle(),
+        ];
+    }
}

テスト用の設定をテスト用のKernelにロードさせる

どの設定ファイルをロードするか指定します。(もちろんテストしたい設定も追加してください)

<?php

+use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\HttpKernel\Kernel;

class TestKernel extends Kernel
{
    public function registerBundles()
    {
        return [
            new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
            new Your\Bundle\YourSpecialBundle(),
        ];
    }

+    public function registerContainerConfiguration(LoaderInterface $loader)
+    {
+        $loader->load(__DIR__.'/config.yml');
+    }
}

テスト用のKernelでファンクショナルテストする

Symfonyが提供しているファンクショナルテスト用のクラスSymfony\Bundle\FrameworkBundle\Test\WebTestCaseがありますが、そのクラスにはテストに使うKernelを指定する事ができます。
毎回Kernelを指定しても問題ないですが手間なのでファンクショナルテスト用のサブクラスを作っておくと便利です。

<?php

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase;

class WebTestCase extends BaseWebTestCase
{
    protected static function getKernelClass()
    {
        return TestKernel::class;
    }
}

あとはいつも通りの手順でバンドルのファンクショナルテストができます。

<?php

class YourFunctionalTest extends WebTestCase
{
    public function testFeature()
    {
        $client = self::createClient();
        $client->request('GET', '/path/to/feature');
    }
}

複数バージョンのSymfonyでファンクショナルテストする

特定のSymfonyだけでなく、サポートする全てのバージョンのSymfonyでテストできればより良いですよね。 そんな時に Travis CI を使えば、複数の環境を1度にテストできて大変捗るのでオススメです。

Travis CI には Build Matrix 機能があり、下図のように全てのバージョンのSymfonyにインストールされた状態を、全てのPHPバージョンでテストする事ができ、殆ど完璧な状態を保つ事が簡単にできます。

  PHP 5.5 PHP 7.0
Symfony v2.8.x Symfony v2.8.x - PHP 5.5 Symfony v2.8.x - PHP 7.0
Symfony v3.1.x Symfony v3.1.x - PHP 5.5 Symfony v3.1.x - PHP 7.0
Symfony v3.2.x Symfony v3.2.x - PHP 5.5 Symfony v3.2.x - PHP 7.0

どのように設定するかはSymfony本家FriendsOfSymfonyのリポジトリが大変参考になります。

まとめ

基本的にはバンドルもアプリケーションと同じ方法でファンクショナルテストができますが、Symfony Standard Edition と違い、初めからファイルが準備されていないので少しだけ準備が必要だというだけでした。本当によくできていて感心します。