作りたいものがありすぎる

40歳を過ぎてプログラミングを始めた人の顛末とこれからなど

Laravelのブラウザテストでテストメソッド毎にシーディングを毎回しない方法

前置き

アプリをある程度作り込んでから、自動テストやTDD(テスト駆動開発)を覚え、いざ自分のアプリで実践しようとした所、かなり手を入れないとろくなユニットテストができない状態という事が分りました。
なにしろユーザーのロール権限が5つもあり、権限毎に表示や動作が異なる箇所が多々ある。(どうしてこうなった)
メソッドの中で他のメソッドを呼びまくり、値を拾っては他に投げ、みたいな入出力の制御が複雑で大変危うい処理もある。
また、例えばあちらのバグをつぶすと、こちらでバグになる。とか、この権限での仕様を変えたけど、他の権限での動作や表示には対応してる?といった確認作業も多くなりました。

で、やむなく現在のアプリを自動ブラウザテストでそれぞれの権限での表示や動作を検証する、という事をしてます。要は自分で画面見て、操作して、というのを機械にやらせる処理を淡々と書く作業です。(正直しんどい)

本題

で、その際によくあるのが setUp() メソッド(テストのメソッド毎に実行される処理)に、シーディング処理を書く奴、こんなのです。

<?php

namespace Tests\Browser;
// use 省略
class Profile_superAdmin_user_Test extends DuskTestCase
{

    use RefreshDatabase;

    protected function setUp()
    {
        parent::setUp();
        Artisan::call('migrate:refresh');
        Artisan::call('db:seed');
    }
    // 以下略
}

この処理なんですが、setUp()はこの下に書かれるブラウザテストの複数のメソッド実行前に毎回実施されます。なので、DBの再構成をテストメソッド毎に毎回やると、テスト実施に結構時間がかかってしまいます。

なので、この中の処理をテストクラスの最初の1回のみ実施される、 setUpBeforeClass()を書いたのですが、理由は忘れましたがうまく行きませんでした。(BrowserテストにsetUpBeforeClass()が無かったか? Artisanコマンドが使えなかった?だったと思います。)

では上手い方法はないか?と調べた所、以下のページの記述に当たりました。

How Do I Seed My Database in the setupBeforeClass Method in a Laravel 4 Unit Test?

日本語直訳サイト
Laravel 4ユニットテストでsetupBeforeClassメソッドにデータベースをシードするにはどうすればよいですか?

<?php

namespace Tests\Browser;
// use 略

class Admin_Community_superAdmin_user_Test extends DuskTestCase
{
    protected static $db_inited = false;
    use RefreshDatabase;

    protected static function initDB()
    {
        Artisan::call('migrate:refresh');
        Artisan::call('db:seed');
    } 

    protected function setUp()
    {
        parent::setUp();
        if (!static::$db_inited) {
            static::$db_inited = true;
            static::initDB();
        }
    }
    // 略
}

setUp() メソッドをあたかもsetUpBeforeClass()の様にクラスインスタンス後に1度だけやってくれる書き方です。
$db_initedという静的プロパティを持たせてsetUp()で一度 true にしてしまったら、 setUp()内の ifの処理で以降は Artisanファサードを呼ばない。という実装。ピタゴラスイッチ的な動きですよね。こういうのって見ればそれなりに動き追えますが、自分で書ける気がしません。が、なにはともあれ解決。

しかしこれだと、テストクラス内でDBの値を変更してしまう処理を書くと、以降のテストに影響がでてしまうのですが、そこは、テストクラスを上手く分けて書いて行けば解決できそうです。

追記

その後以下の様にテストメソッド内に Artisanファサードを使ってDBのシーディングを行う処理を書くことで、さらに必要な時にだけ、シーディングが行えることがわかりました。このまま行くとsetUp()メソッドが無くても何とかなるる感じですね。

<?php

    /**
     * @test
     */
    public function DBを編集するテスト()
    {
        // 省略
    }

    /**
     * @test
     */
    public function 後処理()
    {
        $this->browse(function ($browser) {
            $browser->visit('/user/edit?id=1')
               // assert が書かれて無いとテスト完了時に警告が出る為、便宜上入れた assert
                ->assertSeeIn('.comp-title', 'プロフィール編集');
                echo 'now seeding!';
                Artisan::call('migrate:refresh');
                Artisan::call('db:seed');
        });
    }