LoginSignup
5
6

More than 1 year has passed since last update.

俺はこうして PHPUnit を爆速にした

Last updated at Posted at 2022-08-12

概要

最近、配属されているチームで、ユニットテストコード(PHPUnit)を導入しました。
テストは、きちんと書くと仕様変更や仕様追加時の実装によるバグを検出してくれるので
テストがない時とは精神的な安心感がまるで違いますね。こりゃあ本当に導入して良かったと思っています。
ですが、、、元々全テストケースを回すと7〜10分くらい掛かっており、
何回も回したい自分としてはもう少し早くならないかな〜と思って色々試した結果
下にある画像の様に1600テストの4000アサーションで1分20秒くらいまで短縮しました!
ちなみにテストコード内でモックDBにテストデータの書き込みとかもしていてこの速度なんで
「あれ・・・もしかしてこれ結構いい感じなのでは?」とか一人で思ってたりしていますw
image.png

基本的な考え方

PHPUnit には setUptearDown があります。
これらはテストケース毎に決まった処理を行ってくれる非常にありがたいメソッドですが、
実は裏でLaravelのアプリケーションを立ち上げたり、それを解放したり・・・
などなどやっていて、これが中々コストを食っていてテストを非常に遅くしている様です。
なので、これらをオーバーライドして親のメソッドが呼ばれるのを最低限にします。

実践(基本型)

以下が基本型になります

// 初回読み込み管理変数(初期値false)
private static bool $is_initialized = false;

// setUp をオーバーライド
protected function setUp(): void
{
    // 初回の場合のみ親クラスの setUp を呼び出す
    if (!self::$is_initialized) {
        parent::setUp();
        self::$is_initialized = true;
    } else {
        return;
    }

    // 1回だけ呼びたい処理(DB登録など)
    factory(Hoge::class)->create();
}

// 何も書かないと親クラスでアプリが一度落とされるので tearDown もオーバーライドする
// この時親の tearDown は呼び出さないのがミソ
protected function tearDown(): void { 
    // use Illuminate\Support\Facades\DB;
    // テストコード内でDB書き込みしている場合は接続を切っておく(なければ書かなくてOK)
    // DB書き込みしている場合はこれ書かないとDBのコネクション上限まで達してテストが落ちるので注意!
    DB::connection()->disconnect();
}

まずは static で初回読み込みを完了したことを管理する static 変数を用意して初期値に false を入れておきます。
そして、初回読み込み時に parent::setUp を呼び出し、フラグに true を入れることで2回目以降はアプリケーションの立ち上げは行わない様にしています。
tearDown でもアプリケーションの終了処理などがされているのできっちりオーバーライドして親メソッドを呼ばない様にしておきます。
これでうまくいくと目の前を新幹線が通るかの如く物凄い速さでテストが実行されていきます・・・
おそらくテストの実行速度の遅さに悩んでいる人は結構感動しちゃうんじゃ無いかと思っていますw

例外その1(dispatchを使うテストケース)

元々僕のいたプロジェクトにあったテストコードでは Laravel の dispatch(Queueに突っ込む処理)のテストコードが以下の様に書かれていました。

$this->expectsJobs();

上記のコードは爆速化をするとアサーションとしてみなされなくなりテストが risky になってしまいます。
そこで僕のいたプロジェクトでは以下の様に Bus のモックを作ってテストする方法に書き換えてうまく動くようになりました!

// use Illuminate\Support\Facades\Bus;
Bus::fake();
// 念の為にまだ突っ込まれてないよね〜確認
Bus::assertNotDispatched(DelayedProcess::class);

// ・・・ dispatch に突っ込むロジックを呼び出し例
$dispatch_service->excec();

// キューに処理突っ込まれたよね〜確認
Bus::assertDispatched(DelayedProcess::class);

例外2(sessionを使うテストケース)

このケースがある場合は各テストケース内の先頭で $this-refreshAppliacation() を書くと動きます。
動くんですが、結局全然早くならないのでやった意味がほとんど無いです(笑)
ただし、セッションを使っていない箇所については今まで通り高速で実行してくれるので、
全てのテストケースがsessionを使わないのであれば導入する価値はあるかもしれないです。
もし「こうしたらうまくいくよ〜」というのがあれば是非教えていただきたいです!

まとめ

パッと思いつくのが上であげたものだけですが、思い出したら追記していきます!
最後まで読んでいただきありがとうございました!

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6