概要
背景
テストコードのデザインパターンとしてArrange-Act-Assertパターンというものがあります。
一般的にはこのAAAパターンに従う事で、わかりやすいテストコードが書けますが、実はこの方法で書けない場合があり、ずっと気になっていました。
結論
メソッドの差し替えや返り値の検証を行う必要がなく、メソッドが想定通りに呼び出されたかを検証したい場合は、「スパイ」を使う事で、よりわかりやすく書きなおせる事がわかりました。
詳細
実行環境
- PHP 7.2
 - Laravel 6.0
 
スパイとは?
メソッドの呼び出し情報(コール回数など)を記録し、テスト対象の実行後にアサートできるようにするものです。
スパイに関するより詳しい内容は、以下の記事をご参照ください。
テスト対象
処理の合間に、フィードバックとしてログを出力するメソッドを対象とします。このメソッドを実行した時に、想定通りにログが出力されているかを確認するテストコードを書いていきたいと思います。
use Illuminate\Support\Facades\Log;
class LogClass
{
    public function exec(): void
    {
        Log::info('処理開始');
        // 実際はログの出力の合間に何かしらの処理が行われる想定
        Log::info('処理序盤');
        Log::info('処理中盤');
        Log::info('処理終盤');
        Log::info('処理終了');
    }
}
スパイを使わないテストコード
Laravelにはファサードを最初からモックできるヘルパが準備されているため、以下のように書く事で、想定通りにログが出力されているかを確認する事ができます。
use Tests\TestCase;
use Illuminate\Support\Facades\Log;
class LogTest extends TestCase
{
    public function testスパイを使わない場合()
    {
        // 準備:なし
        // 期待兼アサート
        Log::shouldReceive('info')->with('処理開始');
        Log::shouldReceive('info')->with('処理序盤');
        Log::shouldReceive('info')->with('処理中盤');
        Log::shouldReceive('info')->with('処理終盤');
        Log::shouldReceive('info')->with('処理終了');
        // 実行
        (new LogClass)->exec();
    }
しかし、この書き方だと、以下のような点がテストをわかりにくくしていると思います。
わかりにくい点
- 実行後にアサートを書いていないので、どこで結果の検証を行っているのか伝わりにくい
 - 
shouldReceiveという期待用のメソッドがアサートも兼ねているので、ログの呼び出しをモックしているように見える 
スパイを使った書き方
前述したコードをスパイを使って書き直してみます。
    public function testスパイを使う場合()
    {
        // 準備
        $spy = Log::spy();
        // 実行
        (new LogClass)->exec();
        // アサート
        $spy->shouldHaveReceived('info')->with('処理開始');
        $spy->shouldHaveReceived('info')->with('処理序盤');
        $spy->shouldHaveReceived('info')->with('処理中盤');
        $spy->shouldHaveReceived('info')->with('処理終盤');
        $spy->shouldHaveReceived('info')->with('処理終了');
    }
}
わかりやすくなった点
- AAAパターンに従った書き方をしているので、検証を行っている箇所がわかりやすい
 - 期待用のメソッドではなく、アサート用のメソッドで検証しているので、振る舞いはモックせず元のメソッドを呼び出している事がわかりやすい
 
まとめ
メソッドの差し替えや返り値の検証を行う必要がなく、メソッドの呼び出しを検証したい場合は、是非「スパイ」を使ってみてください!