Laravel3、バリデータークラスのテスト

タグ: Laravel3   テスト志向  

この記事は、Laravel3でのテストについてのシリーズです。

テストの対象は、以下のシリーズで作成した、りんごテーブルに対する簡単なCRUD操作プログラムに対するものです。

バリデタークラスのテスト

テスト対象のAppleValidatorクラスです。

<?php

namespace ProjectH\Services\Validators;

use ProjectH\Exceptions\ExecutionErrorExceptions\ValidationFaildException;

class AppleValidator
{
    public $rules = array (
        'name' => 'required|max:10|unique:apples',
        'color' => 'required|max:10',
    );

    function validate_create( $data )
    {
        $this->excute_validation( $data, $this->rules );
    }

    function validate_update( $id, $data )
    {
        $rules = $this->add_id_with_unique( $id, $this->rules );

        $this->excute_validation( $data, $rules );
    }

    private function excute_validation( $data, $rules )
    {
        $val = \Validator::make( $data, $rules );

        if ( $val->fails() )
        {
            throw new ValidationFaildException( $val,
                'Appleクラスのバリデーションに失敗しました。' );
        }
    }

    protected function add_id_with_unique( $id, $rules )
    {
        foreach ( $rules as $item => $rule )
        {
            if ( preg_match( '/unique:(\w+)(,(\w+))?/', $rule ) > 0 )
            {
                $patterns = explode( '|', $rule );
                $new = array ( );

                foreach ( $patterns as $pattern )
                {
                    if ( preg_match( '/unique:(\w+)(,(\w+))?/', $pattern, $matches ) > 0 )
                    {
                        $unique = 'unique:'.$matches[1];

                        if ( isset( $matches[3] ) )
                        {
                            $unique .= ','.$matches[3].','.$id;
                        }
                        else
                        {
                            $unique .= ','.$item.','.$id;
                        }
                        $new[] = $unique;
                    }
                    else
                    {
                        $new[] = $pattern;
                    }
                }

                $rules[$item] = implode( '|', $new );
            }
        }

        return $rules;
    }

}

今回はテストが長いため、privateの2メソッドについてはvalidate_create()とvalidate_update()のテストでまとめて行ったことにします。(本当はadd_id_with_unique()は単機能として存在しているので、ちゃんとテストするのが正解です。今回はreflectionクラスまで使っていると長過ぎますので、省略です。)

テストクラスは2つに分けました。新規作成時のテストと、更新時のテストです。テストらしいテストを一番行なっているかも知れません。最初は新規作成時のバリデーション単体テストです。

<?php

use ProjectH\Services\Validators\AppleValidator;

class AppleValidatorCreateTest extends \PHPUnit_Framework_TestCase
{
    protected $target;

    public static function setUpBeforeClass()
    {
        Schema::create( 'apples',
            function ($table)
            {
                $table->increments( 'id' );
                $table->string( 'name', 10 );
                $table->string( 'color', 10 );
                $table->timestamps();
            } );

        $apple = new Apple( array (
            'name' => '赤りんご',
            'color' => '赤',
            ) );
        $apple->save();

        $apple = new Apple( array (
            'name' => '青りんご',
            'color' => '青',
            ) );
        $apple->save();
    }

    /*
     * 各テストケース実行前処理
     */

    protected function setUp()
    {
        $this->target = new AppleValidator;
    }

    /*
     * テスト終了時
     */

    public static function tearDownAfterClass()
    {
        Schema::drop( 'apples' );
    }

    /*
     * バリデーション成功時のテスト
     *
     * バリデーションが通らないと例外が発生する
     * 例外が発生するとテストは失敗になる
     */

    public function testOkData()
    {
        $data = array (
            'name' => 'フジ',
            'color' => '藤色',
        );
        $this->target->validate_create( $data );

        $max_length_data = array (
            'name' => '0123456789',
            'color' => '0123456789',
        );
        $this->target->validate_create( $max_length_data );

        $max_length_utf8_data = array (
            'name' => '0123456789',
            'color' => '0123456789',
        );
        $this->target->validate_create( $max_length_utf8_data );
    }

    /*
     * 必須項目指定なし時のエラー
     *
     */

    public function testNoRequiredData1()
    {
        $only_name = array (
            'name' => 'フジ',
        );
        $this->setExpectedException(
            'ProjectH\Exceptions\ExecutionErrorExceptions\ValidationFaildException' );
        $this->target->validate_create( $only_name );
    }

    public function testOverLengthLimited1()
    {
        $name_over = array (
            'name' => '01234567890',
            'color' => '012345',
        );
        $this->setExpectedException(
            'ProjectH\Exceptions\ExecutionErrorExceptions\ValidationFaildException' );
        $this->target->validate_create( $name_over );
    }

    public function testOverLengthLimited2()
    {
        $color_over = array (
            'name' => '012345',
            'color' => '01234567890',
        );
        $this->setExpectedException(
            'ProjectH\Exceptions\ExecutionErrorExceptions\ValidationFaildException' );
        $this->target->validate_create( $color_over );
    }

    public function testOverLengthLimited3()
    {
        $name_over_utf8 = array (
            'name' => '01234567890',
            'color' => '012345',
        );
        $this->setExpectedException(
            'ProjectH\Exceptions\ExecutionErrorExceptions\ValidationFaildException' );
        $this->target->validate_create( $name_over_utf8 );
    }

    public function testOverLengthLimited4()
    {
        $color_over_utf8 = array (
            'name' => '012345',
            'color' => '01234567890',
        );
        $this->setExpectedException(
            'ProjectH\Exceptions\ExecutionErrorExceptions\ValidationFaildException' );
        $this->target->validate_create( $color_over_utf8 );
    }

    public function testSpecifyExistName()
    {
        $name_exist = array (
            'name' => '赤りんご',
            'color' => '012345',
        );
        $this->setExpectedException(
            'ProjectH\Exceptions\ExecutionErrorExceptions\ValidationFaildException' );
        $this->target->validate_create( $name_exist );
    }

}

続いて、更新時のバリデーションテストです。

<?php

use ProjectH\Services\Validators\AppleValidator;

class AppleValidatorUpdateTest extends \PHPUnit_Framework_TestCase
{
    protected $target;

    public static function setUpBeforeClass()
    {
        Schema::create( 'apples',
            function ($table)
            {
                $table->increments( 'id' );
                $table->string( 'name', 10 );
                $table->string( 'color', 10 );
                $table->timestamps();
            } );

        $apple = new Apple( array (
            'name' => '赤りんご',
            'color' => '赤',
            ) );
        $apple->save();

        $apple = new Apple( array (
            'name' => '青りんご',
            'color' => '青',
            ) );
        $apple->save();
    }

    /*
     * 各テストケース実行前処理
     */

    protected function setUp()
    {
        $this->target = new AppleValidator;
    }

    /*
     * テスト終了時
     */

    public static function tearDownAfterClass()
    {
        Schema::drop( 'apples' );
    }

    /*
     * バリデーション成功時のテスト
     *
     * バリデーションが通らないと例外が発生する
     * 例外が発生するとテストは失敗になる
     */

    public function testOkData()
    {
        $data = array (
            'name' => 'フジ',
            'color' => '藤色',
        );
        $this->target->validate_update( '1', $data );

        $max_length_data = array (
            'name' => '0123456789',
            'color' => '0123456789',
        );
        $this->target->validate_update( '1', $max_length_data );

        $max_length_utf8_data = array (
            'name' => '0123456789',
            'color' => '0123456789',
        );
        $this->target->validate_update( '1', $max_length_utf8_data );
    }

    /*
     * 必須項目指定なし時のエラー
     *
     */

    public function testNoRequiredData1()
    {
        $only_name = array (
            'name' => 'フジ',
        );
        $this->setExpectedException(
            'ProjectH\Exceptions\ExecutionErrorExceptions\ValidationFaildException' );
        $this->target->validate_update( '1', $only_name );
    }

    public function testOverLengthLimited1()
    {
        $name_over = array (
            'name' => '01234567890',
            'color' => '012345',
        );
        $this->setExpectedException(
            'ProjectH\Exceptions\ExecutionErrorExceptions\ValidationFaildException' );
        $this->target->validate_update( '1', $name_over );
    }

    public function testOverLengthLimited2()
    {
        $color_over = array (
            'name' => '012345',
            'color' => '01234567890',
        );
        $this->setExpectedException(
            'ProjectH\Exceptions\ExecutionErrorExceptions\ValidationFaildException' );
        $this->target->validate_update( '1', $color_over );
    }

    public function testOverLengthLimited3()
    {
        $name_over_utf8 = array (
            'name' => '01234567890',
            'color' => '012345',
        );
        $this->setExpectedException(
            'ProjectH\Exceptions\ExecutionErrorExceptions\ValidationFaildException' );
        $this->target->validate_update( '1', $name_over_utf8 );
    }

    public function testOverLengthLimited4()
    {
        $color_over_utf8 = array (
            'name' => '012345',
            'color' => '01234567890',
        );
        $this->setExpectedException(
            'ProjectH\Exceptions\ExecutionErrorExceptions\ValidationFaildException' );
        $this->target->validate_update( '1', $color_over_utf8 );
    }

    /*
     * 更新処理の場合、名前の変更がなくてもエラーにならない
     */
    public function testSpecifyExistName()
    {
        $name_exist = array (
            'name' => '赤りんご',
            'color' => '012345',
        );

        $this->target->validate_update( '1', $name_exist );
    }

}

バリデーションのテストですが、applesテーブルを使用しています。これはLaravelのバリデーション機能によります。uniqueルールは、特定のテーブルに項目が含まれていないことを証明するためのルールです。これはとても便利ですが、単体機能を行う場合、ご覧の通りテーブルを用意する必要が起きます。テーブルの準備がなければ、シンプルなテストです。

ですが、uniqueは多用されるバリデーションルールですから、その単体テストのやり方を覚えておいて損はないでしょう。

たった2項目のためにこれだけのテスト量になりました。項目が増えればドンドンとテスト項目は増えます。ユーザーインターフェイスに関わる部分はテスト量が多くなります。ですが、きちんとテストして正しく動作することを証明しておきましょう。(単体できっちりテストしたところは、より上流のテストで多少手を抜けます。抜いても良いよと言われることが多いでしょう。 :D )

ちなみに上記のテストケースで完成度80%程度でしょうか。もっと細かいパターンに対してもテストを書くことができます。入力エリアが増えると組み合わせも増えますから、いくらでもテストは増やせます。それでお金がもらえる場合は、増やすのはかまいませんが、お金にならないのでしたら、知性を使用し程々にしておきましょう。