Laravel3、Eloquentモデルのテスト
この記事は、Laravel3でのテストについてのシリーズです。
- その1:単体テストの準備
 - その2:例外のテスト
 - その3:Eloquentモデルのテスト
 - その4:バリデータークラスのテスト
 - その5:リポジトリークラスのテスト
 - その6:コントローラークラスのテスト
 - その7:結合テスト
 
テストの対象は、以下のシリーズで作成した、りんごテーブルに対する簡単なCRUD操作プログラムに対するものです。
- 参照:Laravel3で活かす例外
 - 参照:クラスのディレクトリー構造をひと工夫
 - 参照:例外と小さなクラス達
 - 参照:小さなクラスで読み込み削除
 - 参照:小さなクラスで更新
 
Eloquentモデルのテスト
EloquentはLaravelのORMです。ORMクラスである限り、テストはデータベース(もしくはそれに類するもの)とのやり取りが必ず含まれます。
今回のプロジェクトで使用しているEloqunet モデルはAppleクラスのみです。
<?php
class Apple extends Eloquent {
}
一番単純なEloquentモデルのコードそのままです。
単体テストではEloquentの機能全部を試す必要はありません。なぜなら、私達はAppleクラスのテストを行うのであり、Eloquentのテストを行うわけでありません。Eloquentの機能がきちんと動作するのを確認するのはLaravelのコアクラスを作成している人達の仕事です。
では、この単純のクラスの何を確認しておくべきでしょうか。
これは単純なクラスの定義ではありますが、隠れている定義があります。一つはテーブル名、一つはタイムスタンプの取り扱いです。
LaravelのEloquentはクラス名を複数形にして小文字にしたものが、そのクラスで取り扱うテーブルになります。つまり、Appleクラスが取り扱うのは'apples'クラスです。
また、デフォルトではレコードのタイムスタンプ、created_atとupdated_atが自動的に更新されます。
コードは何も設定していないようですが、実際はこの2つを確認しておけばよいでしょう。
それではテストコードです。
<?php
class AppleEloquentModelTest extends \PHPUnit_Framework_TestCase
{
    protected $start_time;
    /*
     * テスト開始時
     */
    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();
    }
    /*
     * テスト終了時
     */
    public static function tearDownAfterClass()
    {
        Schema::drop( 'apples' );
    }
    public function testReadAppleFromApplesTable()
    {
        $apple = Apple::find( 1 );
        $this->assertEquals( '赤りんご', $apple->name );
        $this->assertEquals( '赤', $apple->color );
    }
    public function testCreateRead()
    {
        $this->start_time = time();
        $apple = new Apple( array (
            'name' => 'フジ',
            'color' => '黄色',
            ) );
        $apple->save();
        $read_apple = Apple::find( $apple->id );
        $this->assertEquals( 'フジ', $read_apple->name );
        $this->assertEquals( '黄色', $read_apple->color );
        $this->assertGreaterThanOrEqual(
            $this->start_time,
            strtotime( $read_apple->created_at ),
            '開始時間と作成時の比較'
        );
        $this->assertGreaterThanOrEqual(
            $this->start_time,
            strtotime( $read_apple->updated_at ),
            '開始時間と更新時の比較'
        );
        $this->assertGreaterThanOrEqual(
            strtotime( $read_apple->created_at ),
            time(), '作成時と現在時の比較'
        );
        $this->assertGreaterThanOrEqual(
            strtotime( $read_apple->updated_at ),
            time(), '更新時と現在時の比較'
        );
    }
    public function testUpdate()
    {
        $apple = Apple::find( 1 );
        $apple->name = 'ジョナサン';
        $apple->color = '緑';
        $apple->save();
        $read_apple = Apple::find( 1 );
        $this->assertEquals( 'ジョナサン', $read_apple->name );
        $this->assertEquals( '緑', $read_apple->color );
    }
}
setUpBeforeClass()で'apples'テーブルを生成し、レコードを1件追加しています。このテーブルはテスト終了時にtearDownAfterClass()でドロップしています。これを忘れると、同じ'apples'テーブルを使用するテストでエラーになったり、テストが上手く動かなくなります。クリーンアップを忘れずに。
testReadAppleFromApplesTable()で生成しておいた一軒目のレコードをEloquentで読み込んでいます。(本来はIoCを使用しインスタンスを生成するべきですが、IoC登録名と実際のクラス名は一緒なので、今回は省略しています。)このレコードが読み込めたという事は、ただしくapplesテーブルを読み込んでいることを証明しています。
時間の比較はややマニアックですね。最初に時間を保存しておき、レコードを作成し、そのレコードをよみ、生成時間、更新時間を保存しておいた時間、比較実行時と比較しています。
開始時の時間 < レコードの更新/作成時間 < assert時(比較時)の時間
上記のようになるのですが、テストが早すぎ、通常一秒以内で終了してしまうため、本当に動いているのか分かりません。(そのため、assertGreaterThanOrEqual()の指定順番があっているのかも、正直分かりません。:D )
多分、ここまで厳密に行わなくても、created_atとupdated_atが空文字列でなければOKとするだけで十分でしょう。
最後の更新処理は助長ですね。念の為です。
Eloquent ORMの機能テストでないため、全機能を行う必要はありません。今回は設定の内容のチェック+αです。余裕がありましたら、自分のプロジェクトで使用するEloqunetの機能を一通り、テストしておく手もあります。Laravel3の場合、もうこなれているので仕様変更が起きる可能性は低いのですが、新しいフレームワークやメジャーリリース直後は仕様変更が起きる可能性があります。ドキュメントから漏れる可能性もありますので、一通りテストを書いておくのは保証になります。(Laravelも今年の5月にメジャーアップデート版の4になります。4がリリースされたら大きめのプロジェクトで採用しようかなと考えておられる方もいらっしゃるでしょう。)