Help us understand the problem. What is going on with this article?

僕たちとLaravelプロジェクトのテスト変遷〜Mockを差したい〜

概要

この記事はDMMグループ Advent Calendar 2019 20日目の記事です

DMM.comでサーバサイドエンジニアをやっているjuve534です。
現在は配信基盤のリプレイスプロジェクトにて、PHP×Laravelを使ったAPI開発やAWSを使ったインフラ構築を行っています。

今回は、自分たちのAPI開発における、テストコードの変遷を紹介したいと思います。

内容

バージョン1

プロジェクトを始めた当初は、ServiceクラスやRepositoryのテストはインスタンスを new して、テストを書いていました。
コンストラクタインジェクションを使っているので、インジェクションするクラスはMockeryを使い、下記のように書いていました。

class HogeTest extend TestCase
{
    public function testHoge($id)
    {
        // setup
        $mockFogaRepository = Mockery::mock(fugaRepository::class);
        $mockFogaRepository->shouldReceive('何かしら')
            ->once()
            ->with($id)
            ->andReturn();

        // exercise
        $hogeService = new hogeService(
            $mockFogaRepository
        );

        // verify
        $actual = $hogeService->hoge($id);
        $this->assertTrue($actual);
    }
}

この書き方で開発を行っていたのですが、コード量が増えると苦しくなってきました。
コンストラクタインジェクションが増えれば、 HogeTest のテストコードをすべて書き直す必要があるためです。

先程の例でいうと、 hogeService に新しくコンストラクタインジェクションを増やした場合、 testHoge のテストはコケてしまいます。
1ケースなら対応できますが、テストコードによっては100ケース(パラメタライズテスト込み)もあったりするので、コンストラクタインジェクションが増えるたびに書くのは、時間もかかりました。
しかも、ただクラスを生成して渡してあげる修正になるので、時間がかかることと合わせ、精神ゲージが削られました。

そこで、下記のようにテストを書き直していきました。

バージョン2

class HogeTest extend TestCase
{
    public function testHoge($id)
    {
        // setup
        $this->app->bind(fugaRepositoryInterface::class, function () use ($id)) {
            $mock = Mockery::mock(fugaRepository::class);
        $mock->shouldReceive('何かしら')
            ->once()
            ->with($id)
            ->andReturn();

            return $mock;
        });

        // exercise
        $hogeService = resolve(hogeServiceInterface::class);

        // verify
        $actual = $hogeService->hoge($id);
        $this->assertTrue($actual);
    }
}

意識したポイントはサービスコンテナで依存性を解決することでした。
resolve メソッドは、クラスインスタンスを依存解決してくれるので、仮にコンストラクタインジェクションが増えたとしても、サービスコンテナで依存性を吸収してくれました。
モックを差したい処理は、$this->app->bind でモックを返してあげることで、モックを差すことができました。

結果として、下記2つの利点を享受することができ、少しだけハッピーになることができました。

  1. コンストラクタインジェクションが増えても、テストケースを書き直す必要が減った
  2. バージョン1に比べ、不要なモックを刺すことも減った

これで万事解決といいたいところですが、開発が進むとまた問題が出てきました。

バージョン3

class HogeTest extend TestCase
{
    public function testHoge($id)
    {
        // setup
        $this->app->singleton(fugaRepositoryInterface::class, function () use ($id)) {
            $mock = Mockery::mock(fugaRepository::class);
        $mock->shouldReceive('何かしら')
            ->once()
            ->with($id)
            ->andReturn();

            return $mock;
        });

        // exercise
        $hogeService = resolve(hogeServiceInterface::class);

        // verify
        $actual = $hogeService->hoge($id);
        $this->assertTrue($actual);
    }
}

先程との違いは、モックを差す際に、bindからsingletonに変わったことです。
bindは同じインスタンスを返してくれないため、依存関係が複雑になると、モックではなく実体を返してしまうことがありました。
singletonは依存関係の解決を1度にしてくれるため、必ずモックを返してくれました。
そのため、bindでコケた際は、singletonに書き直すようにしました。

※bindでコケたことは自分たちのコードが複雑になっているバロメーターであり、本来は依存関係を正すことが重要だと思います。ただ、現状はそこに手を付けられていないため、singletonで対応するようにしています。

終わりに

自分たちのテストコードの変遷は以上となります。
テストコードで詰まっている人の、少しでも参考になれば幸いです。

最後まで読んでいただきありがとうございました。

参考資料

juve_534
駆け出しから5年ほど保守ばかりやっていたPHPer٩( 'ω' )و 今は新規開発でLaravelやAWSと戯れている。 ScalaやHack、Elixirが気になっている。
dmmcom
総合エンタテイメントサイト「DMM.com」を運営。会員数は2,900万人を突破。動画配信、FX、英会話、ゲーム、太陽光発電、3Dプリンタなど40以上のサービスを展開。沖縄での水族館事業参入、ベルギーでのサッカークラブ経営など、様々な事業を手掛ける。また2018年より若手起業家の支援を強化、「DMM VENTURES」による出資や、M&Aなどを積極的に展開している。
https://dmm-corp.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした