PHPUnitを低速化する技術
この記事はPHP Advent Calendar 2023 24日目の記事です。
かつ、Lancers(ランサーズ) Advent Calendar 2023 の24日目の記事でもあります。
モチベーション
社内でテストが遅い原因を説明する機会がありました。
せっかくですので、社内ドキュメントに書いた内容をこの機会に公開させていただきます。
TL;DR
- Attributesに
@runTestsInSeparateProcesses
を付けるとテストが遅くなる - エイリアス/インスタンスモックを使うと
@runTestsInSeparateProcesses
を使わざるを得ない状況が生まれる
@runTestsInSeparateProcesses
を付けるとテストが遅くなる
AttributesにRunTestsInSeparateProcesses属性は、テストケースクラスのすべてのテストを別々のプロセス(テストごとに1つの別々のプロセス)で実行することを指定するために使用できます。
2. Attributes — PHPUnit 10.5 Manual
このAttributesを付けるとクラス内のテストケースの数だけPHPプロセスが起動するため、プロセス起動のオーバーヘッド分テストが遅くなります。
@runTestsInSeparateProcesses
を付けざるを得ないシチュエーションがある
嫌でもなぜ、テストが遅くなる@runTestsInSeparateProcesses
をわざわざ使う必要があるのでしょうか?
@runTestsInSeparateProcesses
に主に名前の衝突を回避するために利用する機能です。
どのような場面で、テスト時に名前の衝突が発生するのか、モック(mockery)のしくみから説明していきます。
モック(mockery)のしくみ
モック(mockery)のしくみをかいつまんで説明すると下記になります。
- mockeryはモック対象のクラスを継承した別名のクラスを生成する
-
Foo
をモックするとFoo
を継承したMockery_0_Foo
を生成する
-
- 生成したモッククラスでメソッドをオーバーライドすることでモックを実現する
詳細は下記の記事に詳しく載っています。
new
されちゃっている時
staticメソッドをモックしたい、あるいはモック対象クラスがところで、staticメソッドをモックしたい時や、モック対象クラスがnew
されちゃっている時は前述のしくみではモックできません。
staticメソッドの場合は、モックによってMockery_0_Foo
を生成したとしてもソースコード上でFoo::baa()
をコールすると親クラスのメソッドが呼ばれてしまいます。
同様に、new Foo()
されている時はMockery_0_Foo
を生成しても親クラスを参照しているためモックを差し込めません。
そこでmockery::mock(’alias:Foo’)
もしくはmockery::mock(’overload:Foo’)
を使います。
これにより、エイリアス/インスタンスモックが生成され、強い依存関係でもモックを差し込むことができます。
エイリアス/インスタンスモックを作ると名前が衝突する
この時、どこか別の場所でモックせずに親クラスを利用していた場合、親クラスであるFoo
と名前が衝突してエラーになります。
Mockery\Exception\RuntimeException: Could not load mock Foo, class already exists
@runTestsInSeparateProcesses
を使うことでテストケース毎にPHPのプロセスが分離されるため名前の衝突を回避できるのです。
まとめ
本稿では、PHPUnitを低速化する技術として@runTestsInSeparateProcesses
を紹介しました。
依存関係に無頓着でいるとエイリアス/インスタンスモックを使うことになり、最後は@runTestsInSeparateProcesses
を使う羽目になるので注意しましょう。
Discussion