前回の記事の続きです。
-
Symfony2のblogチュートリアルをリファクタリングしてみた(repositoryクラス)
前回は、blogチュートリアルを 10 章まで進め、Fixtureを導入してテストコードを書く準備を整えました。 今回は ...
続きを見る
引き続き、repository と form のテストを書いてみたので紹介します。
動作確認環境
- Symfony 2.0.11
- PHP 5.3.10
- PHPUnit 3.6.10
目次
Repositoryクラスのテストコード
Repositoryクラスのテストは、データベースを正しく操作できているか確認しています。
src/My/BlogBundle/Tests/Repository/PostRepositoryTest.php
<?php
namespace My\BlogBundle\Tests\Repository;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use My\BlogBundle\DataFixtures\ORM\LoadPostData;
use My\BlogBundle\Entity\Post;
class PostRepositoryTest extends WebTestCase
{
/**
* @var \Doctrine\ORM\EntityManager
*/
private $em;
/**
* @var \My\BlogBundle\Repository\PostRepository
*/
private $postRepository;
/**
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
private $container;
public function setUp()
{
$kernel = static::createKernel();
$kernel->boot();
$this->container = $kernel->getContainer();
$loader = new Loader($this->container);
$loader->addFixture(new LoadPostData);
$fixtures = $loader->getFixtures();
$this->em = $this->container->get('doctrine.orm.entity_manager');
$purger = new ORMPurger($this->em);
$purger->setPurgeMode(ORMPurger::PURGE_MODE_TRUNCATE);
$executor = new ORMExecutor($this->em, $purger);
$executor->execute($fixtures);
$this->postRepository = $this->em->getRepository('MyBlogBundle:Post');
}
public function testSearch()
{
$posts = $this->postRepository->search();
$post = $posts[0];
$this->assertSame($post->getTitle(), 'title');
}
public function testSearchOneById()
{
$post = $this->postRepository->searchOneById(1);
$this->assertSame($post->getTitle(), 'title');
}
/**
* @expectedException Doctrine\ORM\NoResultException
*/
public function testSearchOneByIdに存在しないidを指定したら例外を投げる()
{
$post = $this->postRepository->searchOneById(-1);
}
public function testInsert()
{
$post = new Post();
$post->setTitle('title');
$post->setBody('bodybodybody');
$this->assertTrue($this->postRepository->insert($post));
$query = $this->postRepository
->createQueryBuilder('p')
->orderBy('p.id', 'DESC')
->getQuery()
->setMaxResults(1);
$posts = $query->getResult();
$post = $posts[0];
$this->assertSame('title', $post->getTitle());
$this->assertSame('bodybodybody', $post->getBody());
}
public function testDelete()
{
$this->assertTrue($this->postRepository->delete(1));
$query = $this->postRepository
->createQueryBuilder('p')
->where('p.id = :id')
->setParameter('id', 1)
->getQuery();
$posts = $query->getResult();
$this->assertSame(array(), $posts);
}
public function testUpdate()
{
$post = $this->postRepository->searchOneById(1);
$post->setTitle('edit_title');
$post->setBody('edit_bodybodybody');
$this->assertTrue($this->postRepository->update($post));
$query = $this->postRepository
->createQueryBuilder('p')
->where('p.id = :id')
->setParameter('id', 1)
->getQuery();
$posts = $query->getResult();
$post = $posts[0];
$this->assertSame('edit_title', $post->getTitle());
$this->assertSame('edit_bodybodybody', $post->getBody());
}
}
Formクラスのテストコード
Formクラスのテストは、入力値のバリデートが正しく動いているか確認しています。
文字数エラーのメッセージに {{ limit }} の文字が含まれています。twigに出力するためのメッセージと思いますが、この中身を取り出す方法がわかりませんでした。
src/My/BlogBundle/Tests/Form/PostTypeTest.php
<?php
namespace My\BlogBundle\Form;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use My\BlogBundle\Form\PostType;
use My\BlogBundle\Entity\Post;
class PostTypeTest extends WebTestCase
{
private $container;
private $token;
public function setUp()
{
$kernel = static::createKernel();
$kernel->boot();
$this->container = $kernel->getContainer();
$this->token = $this->container->get('form.csrf_provider')->generateCsrfToken('unknown');
}
public function test正常系()
{
$form = $this->container->get('form.factory')->create(new PostType, new Post());
$data['title'] = 'title';
$data['body'] = 'bodybodybody';
$data['_token'] = $this->token;
$form->bind($data);
$this->assertTrue($form->isValid());
}
public function test必須チェック()
{
$form = $this->container->get('form.factory')->create(new PostType, new Post());
$data['title'] = '';
$data['body'] = '';
$data['_token'] = $this->token;
$form->bind($data);
$this->assertFalse($form->isValid());
$childForms = $form->getChildren();
$this->assertTrue($childForms['title']->hasErrors());
$errorForms = $childForms['title']->getErrors();
$errorMessage = $errorForms[0]->getMessageTemplate();
$this->assertSame($errorMessage, 'This value should not be blank');
$this->assertTrue($childForms['body']->hasErrors());
$errorForms = $childForms['body']->getErrors();
$errorMessage = $errorForms[0]->getMessageTemplate();
$this->assertSame($errorMessage, 'This value should not be blank');
}
public function test最小文字数チェック()
{
$form = $this->container->get('form.factory')->create(new PostType, new Post());
$data['title'] = '1';
$data['body'] = '1';
$data['_token'] = $this->token;
$form->bind($data);
$this->assertFalse($form->isValid());
$childForms = $form->getChildren();
$this->assertTrue($childForms['title']->hasErrors());
$errorForms = $childForms['title']->getErrors();
$errorMessage = $errorForms[0]->getMessageTemplate();
$this->assertSame($errorMessage, 'This value is too short. It should have {{ limit }} characters or more');
$this->assertTrue($childForms['body']->hasErrors());
$errorForms = $childForms['body']->getErrors();
$errorMessage = $errorForms[0]->getMessageTemplate();
$this->assertSame($errorMessage, 'This value is too short. It should have {{ limit }} characters or more');
}
public function test最大文字数チェック()
{
$form = $this->container->get('form.factory')->create(new PostType, new Post());
$longCharacter = '';
for ($i=0; $i < 51; $i++) {
$longCharacter .= 'a';
}
$data['title'] = $longCharacter;
$data['body'] = $longCharacter;
$data['_token'] = $this->token;
$form->bind($data);
$this->assertFalse($form->isValid());
$childForms = $form->getChildren();
$this->assertTrue($childForms['title']->hasErrors());
$errorForms = $childForms['title']->getErrors();
$errorMessage = $errorForms[0]->getMessageTemplate();
$this->assertSame($errorMessage, 'This value is too long. It should have {{ limit }} characters or less');
$this->assertFalse($childForms['body']->hasErrors());
}
}
コントローラのテストコードのリファクタリング
RepositoryクラスのテストとFormクラスのテストができましたので、コントローラのテストから重複部分を削除しました。
作業の途中で悩んだのが、コントローラのテストは何をテストするべきなのか?ということです。
Symfony2のマニュアルや、webのリファレンスを見て、とりあえず下記の観点でのテストのみ残すことにしました。
- ルーティングのテスト
- リクエストに対して、レスポンスの内容は期待通りか
- コントローラから、モジュールはきちんと呼び出されているか
モジュールがコントローラから呼び出されているかのテストは、厳密にはできていません。
「リクエストに対してこのレスポンスがあれば、ひとまずモジュールの呼び出しはできているだろう」といったレベルのテストとなっています。
今後、よいテスト方法が見つかれば紹介したいと思います。
src/My/BlogBundle/Tests/Controller/DefaultControllerTest.php
<?php
namespace My\BlogBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Bundle\FrameworkBundle\Client;
use Symfony\Component\DomCrawler\Form;
use Doctrine\Common\DataFixtures\Loader;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use My\BlogBundle\DataFixtures\ORM\LoadPostData;
/**
* DefaultControllerTest
*
* @uses \Symfony\Bundle\FrameworkBundle\Test\WebTestCase
*/
class DefaultControllerTest extends WebTestCase
{
public function setUp()
{
$kernel = static::createKernel();
$kernel->boot();
$loader = new Loader($kernel->getContainer());
$loader->addFixture(new LoadPostData);
$fixtures = $loader->getFixtures();
$em = $kernel->getContainer()->get('doctrine.orm.entity_manager');
$purger = new ORMPurger($em);
$purger->setPurgeMode(ORMPurger::PURGE_MODE_TRUNCATE);
$executor = new ORMExecutor($em, $purger);
$executor->execute($fixtures);
}
/**
* 一覧画面が表示されるかテストする
*/
public function test一覧画面が表示される()
{
$client = static::createClient();
$crawler = $client->request('GET', '/blog/');
$this->assertTrue($client->getResponse()->isSuccessful());
$body = $client->getResponse()->getContent();
$this->assertSame(1, substr_count($body, 'Blog posts'));
}
/**
* 登録ができるかテストする
*
*/
public function test登録ができる()
{
$client = static::createClient();
$crawler = $client->request('GET', '/blog/new');
$this->assertTrue($client->getResponse()->isSuccessful());
$body = $client->getResponse()->getContent();
$this->assertSame(1, substr_count($body, 'Add Post'));
$form = $crawler->selectButton('Save Post')->form();
$form['post[title]'] = 'title';
$form['post[body]'] = 'bodybodybody';
$crawler = $client->submit($form);
$this->assertTrue($client->getResponse()->isRedirection());
$crawler = $client->followRedirect();
$this->assertTrue($client->getResponse()->isSuccessful());
$body = $client->getResponse()->getContent();
$this->assertSame(1, substr_count($body, '記事を追加しました'));
}
/**
* 登録画面のバリデーションが機能しているかテストする
*/
public function test登録画面のバリデーションが機能する()
{
$client = static::createClient();
$crawler = $client->request('GET', '/blog/new');
$form = $crawler->selectButton('Save Post')->form();
$this->登録画面と編集画面のバリデーションが機能する($client, $form);
}
/**
* 詳細画面が表示されるかテストする
*/
public function test詳細画面が表示される()
{
$client = static::createClient();
$crawler = $client->request('GET', '/blog/1/show');
$this->assertTrue($client->getResponse()->isSuccessful());
$body = $client->getResponse()->getContent();
$this->assertSame(1, substr_count($body, 'bodybodybody'));
}
/**
* 削除ができるかテストする
*/
public function test削除ができる()
{
$client = static::createClient();
$crawler = $client->request('GET', '/blog/1/delete');
$this->assertTrue($client->getResponse()->isRedirection());
$crawler = $client->followRedirect();
$this->assertTrue($client->getResponse()->isSuccessful());
$body = $client->getResponse()->getContent();
$this->assertSame(1, substr_count($body, '記事を削除しました'));
}
/**
* 編集ができるかテストする
*/
public function test編集ができる()
{
$client = static::createClient();
$crawler = $client->request('GET', '/blog/1/edit');
$this->assertTrue($client->getResponse()->isSuccessful());
$body = $client->getResponse()->getContent();
$this->assertSame(1, substr_count($body, 'Edit Post'));
$form = $crawler->selectButton('Save Post')->form();
$form['post[title]'] = 'edit_title';
$form['post[body]'] = 'edit_bodybodybody';
$crawler = $client->submit($form);
$crawler = $client->followRedirect();
$this->assertTrue($client->getResponse()->isSuccessful());
$body = $client->getResponse()->getContent();
$this->assertSame(1, substr_count($body, '記事を編集しました'));
}
/**
* 編集画面のバリデーションが機能するかテストする
*/
public function test編集画面のバリデーションが機能する()
{
$client = static::createClient();
$crawler = $client->request('GET', '/blog/1/edit');
$this->assertTrue($client->getResponse()->isSuccessful());
$form = $crawler->selectButton('Save Post')->form();
$this->登録画面と編集画面のバリデーションが機能する($client, $form);
}
/**
* 登録画面と編集画面のバリデーションが機能するかテストする
* 必須チェックのみテストし、他のパターンはformクラスのテストに委ねる
*
* @param Symfony\Bundle\FrameworkBundle\Client $client
* @param Symfony\Component\DomCrawler\Form $form
*/
private function 登録画面と編集画面のバリデーションが機能する(Client $client, Form $form)
{
//必須チェック
$form['post[title]'] = '';
$form['post[body]'] = '';
$crawler = $client->submit($form);
$body = $client->getResponse()->getContent();
$this->assertSame(2, substr_count($body, 'This value should not be blank'));
}
/**
* URLに不正な値を設定した時エラーとなるかテストする
*/
public function testURLに不正な値を設定した時NotFoundを返す()
{
$client = static::createClient();
$crawler = $client->request('GET', '/blog/a/show');
$this->assertTrue($client->getResponse()->isNotFound());
$crawler = $client->request('GET', '/blog/-1/show');
$this->assertTrue($client->getResponse()->isNotFound());
$crawler = $client->request('GET', '/blog/a/delete');
$this->assertTrue($client->getResponse()->isNotFound());
$crawler = $client->request('GET', '/blog/-1/delete');
$this->assertTrue($client->getResponse()->isNotFound());
$crawler = $client->request('GET', '/blog/a/edit');
$this->assertTrue($client->getResponse()->isNotFound());
$crawler = $client->request('GET', '/blog/-1/edit');
$this->assertTrue($client->getResponse()->isNotFound());
}
}
おわりに
4回に渡って紹介したSymfony2のblogチュートリアルの写経は、今回でいったん終了とします。