LaravelにPHPUnitを導入する際に考えたい基本設計

LaravelにPHPUnitを導入する際に考えたい基本設計

LaravelでUnitテストを書くことを考えると、真っ先に選択肢に上がるのはPHPUnitかなと思います。

PHPUnitは標準でLaravelにインストールされており、導入のハードルも低いです。今回は、LaravelでPHPUnitを使うパターンと簡単な書き方についてまとめました。

目次

LaravelでPHPUnitを使う基本的な方法

Laravelは5系なら標準でPHPUnitがインストールされています。(4系以前は確認していません)

今回は、前提としてLaravelの5.7のバージョンで話を進めていきます。

したがって、下記のコマンドを実行してみるとPHPUnitの実行ができます。

./vendor/bin/phpunit

デフォルトでは、phpunitの設定ファイルである ./phpunit.xml の設定が読み込まれ、./tests/Feature/ExampleTest.php と ./tests/Unit/ExampleTest.php の2つのテストファイルが実行されています。

それぞれ、どのような内容のテストが実行されているか確認してみると、

/** 省略 **/

class ExampleTest extends TestCase
{
    /** ... */
    public function testBasicTest()
    {
        $response = $this->get('/');

        $response->assertStatus(200); // ここのテストが実行される
    }
}

とステータスコードが200で返ってくるか?のテストが行われていることがわかります。

単体テストを書くのであれば、この testBasicTest() のようなfunctionを条件分岐ごとに実装していけばいいことがわかります。

PHPUnitで特定のファイルを実行するには

単体テストの数が多くなってくると毎回全てのテストを実行していると非常に時間がかかるため、特定のテストのみを実行したいケースがあります。

そういったときは、特定のファイルを指定してあげることにより、そのファイルだけのテストを実行することができます。

./vendor/bin/phpunit ./tests/Unit/ExampleTest.php

また、パスを指定することすら面倒な場合は、ファイル名の指定のみで実行するテストを制御することも可能です。

./vendor/bin/phpunit --filter=ExampleTest

デフォルトの状態で上記コマンドを実行しても、それほど意味がありませんが、テスト数が増えてくると上記コマンドで実行するテスト数を減らすのも有効になります。

また2番目の書き方であればファイル単位ではなく、メソッド名単位でもテストを実行することができるため、より細かくUnitテストを実行することも可能です。

テストファイルの置き場所について

特に迷うことはないかもしれませんが、テストにあたってファイルは /app のnamespaceと同じように /tests に展開するようにファイルを置くと管理がしやすいかと思います。

例えば、/app/Http/Controllers/UserController.php のテストは、/tests/Http/Controllers/UserControllerTest.php に書くといった具合です。

namespaceをあわせておくとエディタでファイル検索をするのも楽なので、合わせておくといいですね。

データベースや外部APIをテストする場合

単体テストを書くときに迷うポイントとして、DBや外部APIに接続するテストを書くことだと思います。

なぜなら、接続先を外部のエンドポイントに繋ぐことを考えると、ローカル環境からでは

  • 本番と同じデータを取得できない
  • テスト用のエンドポイントを用意できない

といった問題が発生することが考えられるためです。これらの対策としては主に下記の2つの方法があると考えられます。

  • 環境変数で接続先を切り替える
  • mockを作成する

環境変数で接続先を切り替える

おそらく多くのプロジェクトではDBや外部APIのエンドポイントを.envファイルに記述しているでしょう。

その読み込む環境変数をPHPUnit実行時だけ別の環境変数にすることで、接続先を切り替えることができます。

Laravelの公式ドキュメントを見るとわかりますが、プロジェクトのルートで .env.testing ファイルを作ると .env ファイルの代わりに環境変数を取得してくれます。

したがって、 

  • .env には開発環境用のエンドポイント
  • .env.testing にはテスト環境用のエンドポイント

を記述することで、開発環境とテスト環境を切り替えることができます。

mockを作成する

テストを書く人にとってはお馴染みですが、mockを作成するという方法もあります。

一応説明を加えておくと、mockとはテストで必要な値を擬似的に生成する機能を持つモジュールです。mockを使用することで、DBや外部APIに接続する値を擬似的に生成してテストを実行することができます。

例えば、下記のコードを見てみます。

/** 省略 **/

class UserRepository extends TestCase
{
    /** ... */
    public function testGetUser()
    {
        $this->assertInstanceOf(Collection::class, \UserModel::first()); // \User::first()はDB接続エラー
    }
}

もしこのようなテストを書いたとしてもテスト用のDBを用意していない環境では、DBの接続エラーが発生します。

したがって、このようなケースでは、UserModelのmockを作成してあげることで擬似的に $user->first() を実行できるようにしなければなりません。

具体的なmockの作り方

PHPUnitには、mockを作成できるphpunit-mock-objectsというライブラリが組み込まれています。

もちろんこちらを使用してもいいですが、少々クセのあるライブラリなため標準ではMockeryというライブラリを使って実装するやり方がいいでしょう。

上記のテストでmockを作成することを考えると、下記のように実装できます。

/** 省略 **/

class UserRepository extends TestCase
{
    /** ... */
    private function createUserMock()
    {
        //Mockを設定
        $mock = \Mockery::mock(UserModel::class);
        $mock->shouldReceive('first')
            ->andReturn(new Collection());
        return $mock;
    }

    /** ... */
    public function testGetUser()
    {
        $user = $this->createUserMock();
        $this->assertInstanceOf(Collection::class, $user); // テストが成功する
    }
}

上記の例では、createUserMockでfirstというメソッドが実行された場合にCollectionクラスのインスタンスを返すmockを作成しています。

この例は、あまり良くないかもしれませんが、

  • 実行するメソッドを指定した上で
  • 返す値を設定する

ことによって、DBや外部APIから返ってくる値を擬似的に生成します。このようにmockを組み合わせることで、テストのcoverageも大きく改善が見込めます。

どのクラスにおいても80%以上のcoverageを目安にして実装をしていきたいところです。

まとめ

今回は、LaravelでPHPUnitを使用する方法と、その周辺の設計について大まかに解説しました。

テストは長期的にサービスを運用するにあたってはデグレを起こさないためにも必須かと思います。最初は導入コストを考えると見合わないと感じるかもしれませんが、サービスを長く運営しているほどテストの価値を感じると思うのでぜひとも長期的目線でテストを書いていきましょう。

あわせて読みたい
Laravelのinsertはcreateメソッドを使うのがベストな選択肢になる
Laravelのinsertはcreateメソッドを使うのがベストな選択肢になるLaravelにはDatabaseを操作する便利なORMが標準で用意されていますが、結局どれを使うのがベストなのかわからなかったりします。今回は、Laravelを用いてデータベースに...

関連記事

コメント

コメントする

目次