Symfony2 + PHPUnitではじめてのテスト（機能テスト編）


##さっそくやってみよう
導入方法および単体テストについては、下記を参照ください。

[Symfony2 + PHPUnitではじめてのテスト（単体テスト編）](http://qiita.com/imunew/items/cb8e9dac7361cee399ba)

###機能テスト（Controller編）

####テストケースの作成
コントローラーをconsoleから作成すると、テストケースももれなく作成されます。

[Generating a New Controller - Symfony(current)](http://symfony.com/doc/current/bundles/SensioGeneratorBundle/commands/generate_controller.html)

> The generate:controller command generates a new Controller including actions, <strong>tests</strong>, templates and routing.

[ControllerGenerator.php - Github](https://github.com/sensiolabs/SensioGeneratorBundle/blob/master/Generator/ControllerGenerator.php)

```Php:ControllerGenerator.php
$this->renderFile('controller/ControllerTest.php.twig', $dir.'/Tests/Controller/'.$controller.'ControllerTest.php', $parameters);
```

自動生成されたテストケースは下記のようなイメージ

```php
<?php
namespace Sopra\WebBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class MypageControllerTest extends WebTestCase
{
}
```

####環境変数を設定
必要に応じて、phpunit.xmlに変数設定を追加します。

```Xml:phpunit.xml
<phpunit>
    <!-- ... -->
    <php>
        <server name="KERNEL_DIR" value="../app/" />
        <server name="SERVER_NAME" value="local.sopra.jp" />
        <server name="SERVER_PORT" value="80" />
        <server name="SCRIPT_NAME" value="" />
        <server name="REQUEST_URI" value="" />
    </php>
    <!-- ... -->
</phpunit>
```


####テストメソッドの実装
大まかにいうとテストメソッドの処理フローは以下のようになります。

```php
// 'test'で始まるpublic関数を定義
public function testExecuteSuccess() {
        
    // 必要に応じて環境変数を設定
        
    // 準備（データの初期化など）
        
    // ページにアクセス
        
    // テストしたい処理を実行（送信ボタンをクリックなど）
    
    // 結果を検証
}
```

##実践してみよう
実際に実装したときに、つまづいた点などを中心にポイントを解説していきます。

###ログイン
ログインが必要なページをテストする場合は、ログイン状態をシミュレートしなければいけません。
symfony.comに説明があるので、ほぼそのとおり実装しました。

[How to simulate Authentication with a Token in a Functional Test (current) - Symfony](http://symfony.com/doc/current/cookbook/testing/simulating_authentication.html)

```php
private function logIn()
{
    $session = $this->client->getContainer()->get('session');

    $firewall = 'secured_area';
    $token = new UsernamePasswordToken('admin', null, $firewall, array('ROLE_ADMIN'));
    $session->set('_security_'.$firewall, serialize($token));
    $session->save();

    $cookie = new Cookie($session->getName(), $session->getId());
    $this->client->getCookieJar()->set($cookie);
}
```

###テスト用データの投入（Doctrin Fixtures Bundleの導入）
テストを実行するたびにデータを手動で投入していたのでは、意味が無いので、Doctrin Fixtures Bundleを導入することにしました。

[DoctrineFixturesBundle - Symfony](http://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html)

```Json:composer.json
{
    "require": {
        "doctrine/doctrine-fixtures-bundle": "dev-master"
    }
}
```

いったんテーブルをtruncateしてからデータを投入するようにします。

```Php:EntityHelper.php
<?php

namespace Sopra\CoreBundle\DataFixtures\Helper;

use Doctrine\ORM\EntityManager;

class EntityHelper {
    
    private $entityManager;
    
    public function __construct(EntityManager $entityManager) {
        
        $this->entityManager = $entityManager;
    }
    
    public function truncateTables($tables) {
        
        $connection = $this->entityManager->getConnection();
        $platform = $connection->getDatabasePlatform();
        
        $connection->exec('SET FOREIGN_KEY_CHECKS = 0');

        foreach ($tables as $table) {
            $connection->exec($platform->getTruncateTableSQL($table));
        }
        
        $connection->exec('SET FOREIGN_KEY_CHECKS = 1');
    }
    
}
```

'SET FOREIGN_KEY_CHECKS = 0'は外部キーを無視するために実行していますが、mysql固有の関数なので修正必要ですね。

[MySQL 5.1 リファレンスマニュアル :: 13.5.6.4 FOREIGN KEY 制約](http://dev.mysql.com/doc/refman/5.1/ja/innodb-foreign-key-constraints.html)

さらに、テストケースからFixtureをloadするために、別のHelperクラスを実装しています。

```Php:FixturesHelper.php
<?php

namespace Sopra\CoreBundle\DataFixtures\Helper;

use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\DataFixtures\Loader;
use Doctrine\ORM\EntityManager;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

class FixturesHelper {
    
    private $container;
    private $entityManager;
    
    public function __construct(ContainerInterface $container, EntityManager $entityManager) {
        
        $this->container = $container;
        $this->entityManager = $entityManager;
    }
    
    public function loadFixtures($fixtures) {
        
        $loader = new Loader();
        foreach ($fixtures as $fixture) {
            if ($fixture instanceof ContainerAwareInterface) {
                $fixture->setContainer($this->container);
            }
            if ($fixture instanceof FixtureInterface) {
                $loader->addFixture($fixture);
            }
        }
        $executor = new ORMExecutor($this->entityManager);
        $executor->execute($loader->getFixtures(), true);
    }
    
}
```

各Fixtureからサービスコンテナにアクセスするために、ContainerAwareInterfaceを実装します。
sopraでは、EntityHelperに渡すEntityManagerを取得するために、setContainerしています。

[Using the Container in the Fixtures - Symfony](http://symfony.com/doc/current/bundles/DoctrineFixturesBundle/#using-the-container-in-the-fixtures)

各クラス、インターフェイスの関係をクラス図にまとめると下記のようになります。

![data_fixtures.png](https://qiita-image-store.s3.amazonaws.com/0/26422/18aafc8e-b9c0-3f92-7ddf-27e909c17977.png "data_fixtures.png")


###ページにアクセス
ページへのアクセスはSymfony\Bundle\FrameworkBundle\Clientオブジェクトを通じて行いますので、setupメソッドにて生成し、メンバ変数にセットしておきます。

```Php:BaseWebTestCase.php
<?php
namespace Sopra\WebBundle\Tests\TestCase;

use Symfony\Bundle\FrameworkBundle\Client;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

abstract class BaseWebTestCase extends WebTestCase {

    /** @var Client */
    private $client;

    protected function setUp() {
        
        parent::setUp();
        
        $this->client = static::createClient();

    }
}
```

Client::requestメソッドにてページにアクセスします。
リダイレクトされる場合はfollowRedirectし、戻り値のcrawlerオブジェクトを取得します。

```php
$this->getClient()->request('GET', '/path/to/page');
$crawler = $this->getClient()->followRedirect();
/* @var $crawler Symfony\Component\DomCrawler\Crawler */
```

###テストしたい処理を実行
'Submit'ボタンをクリックして、レスポンスを取得する場合は、下記のような実装になります。

```php
$form = $crawler->selectButton('Submit')->form();
        
$this->getClient()->submit($form);
$crawler = $this->getClient()->followRedirect();
```

###結果を検証
結果ページに'Success'という文字列が含まれているかどうかを確認するには、下記のような実装になります。

```php
$this->assertTrue($crawler->filter('html:contains("Success")')->count() > 0);
```

'Success'と言う文字列が一つも含まれない場合は、その時点でテスト失敗となります。
上記のassertTrue意外にも、PHPUnitにはあらかじめ様々なアサーションメソッドが用意されています。

[アサーション - phpunit](http://phpunit.de/manual/3.7/ja/writing-tests-for-phpunit.html#writing-tests-for-phpunit.assertions)


##テスト実行

```bash
$ cd bin
$ ./phpunit -c ../app/ ../src/Sopra/WebBundle/Tests/Controller/SampleControllerTest.php
PHPUnit 3.7.27 by Sebastian Bergmann.

Configuration read from /path/to/Application Root/app/phpunit.xml

.....

Time: 7.95 seconds, Memory: 51.50Mb

OK (5 tests, 33 assertions)
```