Edited at

Symfony2 + PHPUnitではじめてのテスト(機能テスト編)

More than 5 years have passed since last update.


さっそくやってみよう

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

Symfony2 + PHPUnitではじめてのテスト(単体テスト編)


機能テスト(Controller編)


テストケースの作成

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

Generating a New Controller - Symfony(current)


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


ControllerGenerator.php - Github


ControllerGenerator.php

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


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

<?php

namespace Sopra\WebBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class MypageControllerTest extends WebTestCase
{
}


環境変数を設定

必要に応じて、phpunit.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>


テストメソッドの実装

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

// 'test'で始まるpublic関数を定義

public function testExecuteSuccess() {

// 必要に応じて環境変数を設定

// 準備(データの初期化など)

// ページにアクセス

// テストしたい処理を実行(送信ボタンをクリックなど)

// 結果を検証
}


実践してみよう

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


ログイン

ログインが必要なページをテストする場合は、ログイン状態をシミュレートしなければいけません。

symfony.comに説明があるので、ほぼそのとおり実装しました。

How to simulate Authentication with a Token in a Functional Test (current) - Symfony

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


composer.json

{

"require": {
"doctrine/doctrine-fixtures-bundle": "dev-master"
}
}

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


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 制約

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


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

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

data_fixtures.png


ページにアクセス

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


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オブジェクトを取得します。

$this->getClient()->request('GET', '/path/to/page');

$crawler = $this->getClient()->followRedirect();
/* @var $crawler Symfony\Component\DomCrawler\Crawler */


テストしたい処理を実行

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

$form = $crawler->selectButton('Submit')->form();

$this->getClient()->submit($form);
$crawler = $this->getClient()->followRedirect();


結果を検証

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

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

'Success'と言う文字列が一つも含まれない場合は、その時点でテスト失敗となります。

上記のassertTrue意外にも、PHPUnitにはあらかじめ様々なアサーションメソッドが用意されています。

アサーション - phpunit


テスト実行

$ 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)