Laravel3、バリデータークラスのテスト
この記事は、Laravel3でのテストについてのシリーズです。
- その1:単体テストの準備
- その2:例外のテスト
- その3:Eloquentモデルのテスト
- その4:バリデータークラスのテスト
- その5:リポジトリークラスのテスト
- その6:コントローラークラスのテスト
- その7:結合テスト
テストの対象は、以下のシリーズで作成した、りんごテーブルに対する簡単なCRUD操作プログラムに対するものです。
- 参照:Laravel3で活かす例外
- 参照:クラスのディレクトリー構造をひと工夫
- 参照:例外と小さなクラス達
- 参照:小さなクラスで読み込み削除
- 参照:小さなクラスで更新
バリデタークラスのテスト
テスト対象の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%程度でしょうか。もっと細かいパターンに対してもテストを書くことができます。入力エリアが増えると組み合わせも増えますから、いくらでもテストは増やせます。それでお金がもらえる場合は、増やすのはかまいませんが、お金にならないのでしたら、知性を使用し程々にしておきましょう。