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