読者です 読者をやめる 読者になる 読者になる

はらへり日記

腹に弾丸

LaravelのテストをPHPUnitで書く時にやってる工夫

テスト Laravel PHP Advent Calender

はじめに

この記事はアイスタイルアドベントカレンダー4日目の記事です。

1日目に記事を書いたばかりな気もしますがたぶん気のせいですね。

qiita.com

弊社については2日めに先輩の@ktarow氏が書いたこちらの記事をどうぞ!

アドベントカレンダー - 複数の言語を同時に触ることになった俺は - Qiita

今日のお話

今日はLaravelのテストをPHPUnitで書く際に行ってるちょっとした工夫についてお話します。

Laravelのバージョンは5.1、PHPUnitは4.*代を想定しています。

テストを書くということ

みなさんエンジニアであれば当然、テストを書いていると思います。

テストを書くことには実に様々なメリットがありますが、僕が特に強く感じているのは以下の2つの点です。

  • 後から手を加える時にバグを察知しやすい
  • コードが綺麗になる

わたしは新卒1年目なので今年の研修期間2ヶ月で社内向けサービスを作成したのですが、その際にテストの恩恵を大きく感じながら開発を進めていました。

しかし開発が進むにつれていくつか苦しいポイントが出てきました。

テストを書いててつらいこと

今回、アプリの規模が小規模だったので簡易MVCアーキテクチャとして採用し、サービス、モデルクラスは全てInterfaceに依存。

全てサービスコンテナに突っ込むという方法をとっていました。

そもそも、その設計自体突っ込みどころが満載なのは置いておくとして、それに対してテストを書いてると以下のつらみが出てきました。

Interface継承したモッククラスを作るのがつらい

あるクラスの単体テストを書く時、そのクラスが依存している(コンストラクタでタイプヒントしている)のがインターフェースだと、テストの際に渡すインスタンスもそのインターフェースをimplementsしている必要があります。

例えばこんなクラスがあって

class Pizza
{
    public function __construct(CheezeInterface $cheeze)
    {
        $this->cheeze = $cheeze;
    }
}

これをテストしたくて、Pizzaクラスを生成しようと思うとどこかからCheezeInterfaceを継承したモッククラスを作成する必要があります。

Mockeryを使う、もしくはサービスコンテナの中身をバインドして別のモッククラスに差し替えるという2つの方法がありえますが、どちらもインターフェースを継承するために全てのメソッドを実装する必要が出てきます。

Interfaceのメソッドを足す/減らす時のテストの修正量が肥大化

わたしはモッククラスをMockeryでなく、テストファイル内にインターフェースを継承させたモッククラスを作りサービスコンテナの中身をそれに差し替えるという手法をとっていました。

この方法が個人的には楽でしたし、後から見ても読みやすいのですがテストの量が増えてくるとインターフェースメソッドの引数を1つ直すだけでファイルを5個修正しなきゃいけない、というようなことが頻発しました。

それだけ依存関係が出ている時点でリファクタリングのタイミングでありつつも、何を修正するにも修正箇所が増えてつらい事態となっていました。

解決策

これらの問題を解決するために僕がしているちょっとした工夫をご紹介します。

インターフェースを継承したモッククラスを親クラスに書いておく

LaravelでPHPUnitを使用する際、デフォルトの状態であればtests/TestCase.phpが作成されており、全てのテストクラスはこれを継承してテストを行います。

なのでそこでモック用のクラスを作成しておくことで少し記述量を減らすことが可能です。

例えばPizzaTest.phpGratinTest.phpの2つでCheezeInterfaceを継承したクラスが欲しいとします。

その場合は以下の様に表現します。

TestCase.php

class TestCase extends Illuminate\Foundation\Testing\TestCase
{
}

class MockCheeze implements CheezeInterface
{
    public function nobiru($length) {}
    public function chizimu($length) {}
    public function melt() {}
}

PizzaTest.php

class PizzaTest extends \TestCase
{
}

// ここでMockCheezeをextendsしてモッククラスを作る
class MocCheezeForPizza extends \MockCheeze
{
    public function nobiru($length)
    {
        // 必要なメソッドだけオーバーライドする
    } 
}

GratinTest.php

class GratinTest extends \TestCase
{
}

// ここでMockCheezeをextendsしてモッククラスを作る
class MocCheezeForGratin extends \MockCheeze
{
    public function melt()
    {
        // 必要なメソッドだけオーバーライドする
    } 
}

こう書いておくことにより、以下の様な恩恵が受けられます。

  • CheezeInterfaceの実装メソッドが増えた際、修正するのはTestCaseMockCheezeのみ
  • 各テストクラスは必要なインターフェースを継承したモッククラスをextendsし、テストに必要なメソッドのみオーバーライドする

これで少しばかり、テストの修正量や記述量を減らすことができます。

まとめ

今回学んだこととして、テストコードはアプリのため、自分のためのチェック機構ですが、それを保守したり修正するのも自分たちということを忘れないようにしたいなと思いました。

テストで煮詰まるとつい通すことが優先してその場しのぎのゴリ押しテストを書いたりしてしまいますが、それを後から修正する人の気持ちになって、アプリケーションコードと同じように愛を持って、効率よくかけると良いと思います。

明日は@kubotak氏により「Riot.jsでSPAを作る」です!お楽しみに!