<?xml version="1.0" encoding="UTF-8"?>
<!-- Reviewed: no -->
<!-- EN-Revision: 24249 -->
<sect2 id="zend.test.phpunit.examples">
    <title>例</title>

    <para>
        テスト環境の設定方法とアサーションの作成方法を説明しましたが、
        まだまだ戦いは続きます。それでは、
        実際のテストシナリオをもとにテストの方法を確認していきましょう。
    </para>

    <example id="zend.test.phpunit.examples.userController">
        <title>UserController のテスト</title>

        <para>
            ウェブサイトの一般的なタスクである、
            ユーザ認証とユーザ登録について考えてみましょう。
            今回の例では UserController でこれらを処理することにします。
            要件は次のとおりです。
        </para>

        <itemizedlist>
            <listitem><para>
                ユーザがまだ認証を済ませていない場合は、
                どんなアクションが指定されたかにかかわらず
                常にコントローラのログインページにリダイレクトされる。
            </para></listitem>

            <listitem><para>
                ログインフォームのページには、
                ログインフォームと新規登録フォームの両方が表示される。
            </para></listitem>

            <listitem><para>
                間違った認証情報を入力すると、
                ログインフォームに戻る。
            </para></listitem>

            <listitem><para>
                正しい認証情報を入力すると、
                ユーザのプロファイルページにリダイレクトされる。
            </para></listitem>

            <listitem><para>
                プロファイルページには、そのユーザのユーザ名が表示される。
            </para></listitem>

            <listitem><para>
                認証済みのユーザがログインフォームを訪れると、
                そのユーザのプロファイルページにリダイレクトされる。
            </para></listitem>

            <listitem><para>
                ログアウトしたら、ログインページにリダイレクトされる。
            </para></listitem>

            <listitem><para>
                無効なデータが渡された場合は、登録に失敗する。
            </para></listitem>
        </itemizedlist>

        <para>
            もちろんこれら以外にも別のテストも必要でしょうが、
            今のところはひとまずこれだけにしておきます。
        </para>

        <para>
            今回のアプリケーションでは、プラグイン 'Initialize'
            を定義してそれを <methodname>routeStartup()</methodname> で実行します。
            これによって起動処理をオブジェクト指向でカプセル化することができ、
            コールバックを提供しやすくなります。
            それではまず、このクラスの基本部分を見ていきましょう。
        </para>

        <programlisting language="php"><![CDATA[
class Bugapp_Plugin_Initialize extends Zend_Controller_Plugin_Abstract
{
    /**
     * @var Zend_Config
     */
    protected static $_config;

    /**
     * @var string 現在の環境
     */
    protected $_env;

    /**
     * @var Zend_Controller_Front
     */
    protected $_front;

    /**
     * @var string アプリケーションのルートパス
     */
    protected $_root;

    /**
     * コンストラクタ
     *
     * 環境、ルートパス、設定を初期化します
     *
     * @param  string $env
     * @param  string|null $root
     * @return void
     */
    public function __construct($env, $root = null)
    {
        $this->_setEnv($env);
        if (null === $root) {
            $root = realpath(dirname(__FILE__) . '/../../../');
        }
        $this->_root = $root;

        $this->initPhpConfig();

        $this->_front = Zend_Controller_Front::getInstance();
    }

    /**
     * ルートの開始処理
     *
     * @return void
     */
    public function routeStartup(Zend_Controller_Request_Abstract $request)
    {
        $this->initDb();
        $this->initHelpers();
        $this->initView();
        $this->initPlugins();
        $this->initRoutes();
        $this->initControllers();
    }

    // この後にメソッド定義が続きます...
}
]]></programlisting>

        <para>
            これで、起動用コールバックを次のように作れるようになります。
        </para>

        <programlisting language="php"><![CDATA[
class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
    public function appBootstrap()
    {
        $controller = $this->getFrontController();
        $controller->registerPlugin(
            new Bugapp_Plugin_Initialize('development')
        );
    }

    public function setUp()
    {
        $this->bootstrap = array($this, 'appBootstrap');
        parent::setUp();
    }

    // ...
}
]]></programlisting>

        <para>
            ここまでできたら、テストを書くことができます。
            しかし、ユーザがログインした状態でのテストはどのように書けばいいでしょう?
            簡単な方法は、アプリケーションのロジックを利用する方法です。
            <methodname>resetRequest()</methodname> メソッドや
            <methodname>resetResponse()</methodname> メソッドを使ってちょっとした細工を行い、
            別のリクエストをディスパッチさせます。
        </para>

        <programlisting language="php"><![CDATA[
class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
    // ...

    public function loginUser($user, $password)
    {
        $this->request->setMethod('POST')
                      ->setPost(array(
                          'username' => $user,
                          'password' => $password,
                      ));
        $this->dispatch('/user/login');
        $this->assertRedirectTo('/user/view');

        $this->resetRequest()
             ->resetResponse();

        $this->request->setPost(array());

        // ...
    }

    // ...
}
]]></programlisting>

        <para>
            ではテストを書いてみましょう。
        </para>

        <programlisting language="php"><![CDATA[
class UserControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
    // ...

    public function testCallWithoutActionShouldPullFromIndexAction()
    {
        $this->dispatch('/user');
        $this->assertController('user');
        $this->assertAction('index');
    }

    public function testLoginFormShouldContainLoginAndRegistrationForms()
    {
        $this->dispatch('/user');
        $this->assertQueryCount('form', 2);
    }

    public function testInvalidCredentialsShouldResultInRedisplayOfLoginForm()
    {
        $request = $this->getRequest();
        $request->setMethod('POST')
                ->setPost(array(
                    'username' => 'bogus',
                    'password' => 'reallyReallyBogus',
                ));
        $this->dispatch('/user/login');
        $this->assertNotRedirect();
        $this->assertQuery('form');
    }

    public function testValidLoginShouldRedirectToProfilePage()
    {
        $this->loginUser('foobar', 'foobar');
    }

    public function testAuthenticatedUserShouldHaveCustomizedProfilePage()
    {
        $this->loginUser('foobar', 'foobar');
        $this->request->setMethod('GET');
        $this->dispatch('/user/view');
        $this->assertNotRedirect();
        $this->assertQueryContentContains('h2', 'foobar');
    }

    public function
        testAuthenticatedUsersShouldBeRedirectedToProfileWhenVisitingLogin()
    {
        $this->loginUser('foobar', 'foobar');
        $this->request->setMethod('GET');
        $this->dispatch('/user');
        $this->assertRedirectTo('/user/view');
    }

    public function testUserShouldRedirectToLoginPageOnLogout()
    {
        $this->loginUser('foobar', 'foobar');
        $this->request->setMethod('GET');
        $this->dispatch('/user/logout');
        $this->assertRedirectTo('/user');
    }

    public function testRegistrationShouldFailWithInvalidData()
    {
        $data = array(
            'username' => 'This will not work',
            'email'    => 'this is an invalid email',
            'password' => 'Th1s!s!nv@l1d',
            'passwordVerification' => 'wrong!',
        );
        $request = $this->getRequest();
        $request->setMethod('POST')
                ->setPost($data);
        $this->dispatch('/user/register');
        $this->assertNotRedirect();
        $this->assertQuery('form .errors');
    }
}
]]></programlisting>

        <para>
            これらは簡潔なものであり、大半は実際の中身までは見ていないことに注意しましょう。
            その代わりに、レスポンスコードやヘッダ、そして DOM ノードを見ています。
            これにより、期待通りの構造になっているかどうかを検証できるようになり、
            新たなコンテンツが追加されるたびにテストを実行しなおすことが避けられます。
        </para>

        <para>
            ドキュメントの構造を使用してテストを行なっていることに注目しましょう。
            たとえば最後のテストでは、"errors"
            というクラスが指定されているノードをフォームから探しました。
            これにより、単にフォームの検証エラーが発生したかどうかだけを確認することができ、
            どんなエラーが発生したのかという中身までは気にしなくてすむのです。
        </para>

        <para>
            このアプリケーションでは、データベースを使うことがあるかもしれません。
            そんな場合は、何らかの scaffold を使用してデータベースの初期状態を作成し、
            テスト用の設定を行うという作業が各テストの最初に発生します。
            PHPUnit にはそのための機能が既に用意されています。
            <ulink
                url="http://www.phpunit.de/manual/3.4/en/database.html">
                PHPUnit のドキュメントを参照ください</ulink>。
            テスト時と実運用時には別のデータベースを使用することを推奨します。
            また、特に (ファイルあるいはインメモリ形式の) SQLite
            を使うことを推奨します。どちらも別のサーバを必要とせず、
            大半の <acronym>SQL</acronym> 構文を使用できます。
        </para>
    </example>
</sect2>
<!--
vim:se ts=4 sw=4 et:
-->
