Symfony2 + PHPUnitではじめてのテスト(機能テスト編)
さっそくやってみよう
導入方法および単体テストについては、下記を参照ください。
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
$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>
<!-- ... -->
<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() {
// 必要に応じて環境変数を設定
// 準備(データの初期化など)
// ページにアクセス
// テストしたい処理を実行(送信ボタンをクリックなど)
// 結果を検証
}
実践してみよう(sopraの場合)
sopraで実際に実装したときに、つまづいた点などを中心にポイントを解説していきます。
ログイン
ログインが必要なページをテストする場合は、ログイン状態をシミュレートしなければいけません。
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
{
"require": {
"doctrine/doctrine-fixtures-bundle": "dev-master"
}
}
sopraでは、いったんテーブルをtruncateしてからデータを投入するようにしています。
<?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クラスを実装しています。
<?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
各クラス、インターフェイスの関係をクラス図にまとめると下記のようになります。
ページにアクセス
ページへのアクセスはSymfony\Bundle\FrameworkBundle\Clientオブジェクトを通じて行いますので、setupメソッドにて生成し、メンバ変数にセットしておきます。
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にはあらかじめ様々なアサーションメソッドが用意されています。
実装サンプル
テスト実行
$ cd bin
$ ./phpunit -c ../app/ ../src/Sopra/WebBundle/Tests/Controller/WalletControllerTest.php
PHPUnit 3.7.27 by Sebastian Bergmann.
Configuration read from /Users/imura/workspace/sopra/app/phpunit.xml
.....
Time: 7.95 seconds, Memory: 51.50Mb
OK (5 tests, 33 assertions)