テストコードを書きながらSymfony2のblogチュートリアルを写経した

2012年3月18日

symfony-logo

Symfony2のblogチュートリアルにファンクショナルテストを書きながら写経してみました。

動作確認環境

  • Symfony 2.0.11
  • PHP 5.3.10
  • PHPUnit 3.6.10

目次

  1. テストを書く前の準備
  2. 6章 参照系ページのファンクショナルテスト
  3. 7章 登録ページのファンクショナルテスト
  4. 8章 登録ページのバリデーションのファンクショナルテスト
  5. 10章までチュートリアルを進める
  6. fixtureでテストの順番の依存性を解決

テストを書く前の準備

コマンドラインからSymfony2のテストを実行できるようにしておきます。下記は、Symfony2の本体が ~/Sites/Symfony にある場合の例です。

$ cd ~/Sites/Symfony
$ phpunit -c app
Configuration read from /Users/karakaram/Sites/Symfony/app/phpunit.xml.dist
........
Time: 3 seconds, Memory: 33.50Mb
OK (8 tests, 22 assertions)

-c オプションで phpunit.xml.dist のディレクトリを指定する所がポイントです。引数にファイルを指定すればそのファイルのテストだけを実行することもできます。

6章 参照系ページのファンクショナルテスト

まずはblogチュートリアル(6) テンプレートの作成までチュートリアルを進めます。ここまで完了すると、ブログの一覧を表示するページとブログの詳細を表示するページができているはずです。

これらのページのファンクショナルテストを以下のように書きました。なお、テストを成功させるにはPostテーブルにあらかじめデータを登録しておく必要があります。

src/My/BlogBundle/Tests/Controller/DefaultControllerTest.php

namespace My\BlogBundle\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class DefaultControllerTest extends WebTestCase
{
    public function test一覧画面が表示される()
    {
        $client = static::createClient();
        $crawler = $client->request('GET', '/blog/');
        $this->assertTrue($client->getResponse()->isSuccessful());
    }

    public function test詳細画面が表示される()
    {
        $client = static::createClient();
        $crawler = $client->request('GET', '/blog/1/show');
        $this->assertTrue($client->getResponse()->isSuccessful());
    }
}

ざっくり解説すると下記のことを行なっています。

  • /blog/ と /blog/1/show にGETリクエストを送信。
  • それぞれのリクエストに対し、http status 200 を応答するか検証。

7章 登録ページのファンクショナルテスト

続いて、blogチュートリアル(7) 記事の追加までチュートリアルを進めます。この章では、ブログを登録するページを作成します。

登録ページのファンクショナルテストは以下のように書きました。詳細ページのテストも修正しています。

src/My/BlogBundle/Tests/Controller/DefaultControllerTest.php

//中略
class DefaultControllerTest extends WebTestCase
{
    //中略
    public function test登録ができる()
    {
        $client = static::createClient();

        $crawler = $client->request('GET', '/blog/new');
        $this->assertTrue($client->getResponse()->isSuccessful());

        $form = $crawler->selectButton('Save Post')->form();
        $form['form[title]'] = 'title';
        $form['form[body]'] = 'bodybodybody';
        $crawler = $client->submit($form);
        $crawler = $client->followRedirect();
        $this->assertTrue($client->getResponse()->isSuccessful());

        //データベースを参照して登録されているか確認
        $kernel = static::createKernel();
        $kernel->boot();
        $em = $kernel->getContainer()->get('doctrine.orm.entity_manager');
        $dql = 'SELECT p FROM My\BlogBundle\Entity\Post p ORDER BY p.id DESC';
        $query = $em->createQuery($dql);
        $query->setMaxResults(1);
        $posts = $query->execute();
        $post = $posts[0];
        $this->assertSame('title', $post->getTitle());
        $this->assertSame('bodybodybody', $post->getBody());
    }

    public function test詳細画面が表示される()
    {
        $client = static::createClient();
        // $crawler = $client->request('GET', '/blog/1/show');
        $crawler = $client->request('GET', '/blog/');
        $link = $crawler->filter('a:contains("title")')->eq(0)->link();
        $crawler = $client->click($link);
        $this->assertTrue($client->getResponse()->isSuccessful());
    }
}

テストコードの解説をすると

  • /blog/new ページにリクエストを送り、http status 200 を応答するか検証。
  • formに値を設定し、formをsubmit。submit後、http status 200 を応答するか検証。
  • データベースに接続し、Postテーブルにデータが登録されているか検証。
  • 詳細ページのテストを登録したデータを参照するようにリファクタリング。

8章 登録ページのバリデーションのファンクショナルテスト

続いて、blogチュートリアル(8) データのバリデーションまでチュートリアルを進めます。この章では、登録するページに入力エラーチェックを追加します。

バリデーションのファンクショナルテストは以下のように書きました。

src/My/BlogBundle/Tests/Controller/DefaultControllerTest.php

//中略
class DefaultControllerTest extends WebTestCase
    //中略
    public function test登録画面のバリデーションが機能する()
    {
        $client = static::createClient();
        $crawler = $client->request('GET', '/blog/new');
        $this->assertTrue($client->getResponse()->isSuccessful());

        $form = $crawler->selectButton('Save Post')->form();

        //必須チェック
        $form['form[title]'] = '';
        $form['form[body]'] = '';
        $crawler = $client->submit($form);
        $body = $client->getResponse()->getContent();
        $this->assertSame(2, preg_match_all('/This value should not be blank/', $body, $maches));

        //最小文字数チェック
        $form['form[title]'] = '1';
        $form['form[body]'] = '1';
        $crawler = $client->submit($form);
        $body = $client->getResponse()->getContent();
        $this->assertSame(1, preg_match_all('/This value is too short. It should have 2 characters or more/', $body, $maches));
        $this->assertSame(1, preg_match_all('/This value is too short. It should have 10 characters or more/', $body, $maches));

        //最大文字数チェック
        $longCharcter = '';
        for ($i=0; $i < 51; $i++) {
            $longCharcter .= 'a';
        }
        $form['form[title]'] = $longCharcter;
        $form['form[body]'] = $longCharcter;
        $crawler = $client->submit($form);
        $body = $client->getResponse()->getContent();
        $this->assertSame(1, preg_match_all('/This value is too long. It should have 50 characters or less/', $body, $maches));
    }
    //中略

解説です

  • /blog/new ページにリクエストを送り、http status 200 を応答するか検証。
  • formにエラーとなる値を設定し、submit。
  • http status 200を応答し、期待するエラーメッセージが表示されるか検証。

この程度の文字列マッチであれば、preg_match_all ではなく substr_count が使えそうなことを後から知りました。そのうちリファクタリングします。

10章までチュートリアルを進める

ここまでテストを書いて感触をつかめれば、後はなんとかなると思います。テストコードを書きながら、10章までチュートリアルを進めます。残りの章では記事の削除、記事の編集などが紹介されています。

10章以降はリファクタリングと新機能追加について紹介されています。10章に入る前にもう少しテストの実行環境を整えます。

fixtureでテストの順番の依存性を解決

データの削除の章までテストを書くと、テストの順番やデータの状況によっては詳細ページのテストが失敗するようになります。参照ページや編集ページのテストも、初期データがないとテストが失敗してしまいます。テストの順番に依存性が発生しており、好ましい状況ではありません。

次回は、DoctrineFixturesBundleを導入してこの問題を解決します。

symfony-logo
[Symfony2] DoctrineFixturesBundleをPHPUnitと連動させる方法

前回の記事で、Symfony2のblogチュートリアルにファンクショナルテストを書きました。 このテストにはテストメソッ ...

続きを見る

-技術ブログ
-