Edited at

PHPUnitの@dataProviderはforループの40倍遅い

PHPUnitにはテストデータを駆動してテストケースを実行するData Providerという機能があるが、それは一つのテストケースでforループするより40倍遅い。


テストコード

検証のために次のようなテストコードを用意した。完全版はGitHubに置いてある。


UsingDataProviderTest.php

final class UsingDataProviderTest extends TestCase

{
/**
* @dataProvider trues
*/

public function test_true(bool $value): void
{
self::assertTrue($value);
}

public function trues(): iterable
{
for ($count = 1; $count <= 1000; $count++) {
yield [true];
}
}
}



UsingLoopTest.php

final class UsingLoopTest extends TestCase

{
public function test_true(): void
{
foreach ($this->trues() as $value) {
self::assertTrue($value);
}
}

public function trues(): iterable
{
for ($count = 1; $count <= 1000; $count++) {
yield true;
}
}
}



実行結果


  • UsingDataProviderTest: 474ms

  • UsingLoopTest: 12ms

40倍くらい速さが違う


なぜdataProviderは遅いか?

dataProviderの遅さの原因仮説としてはこう考えられる。dataProviderはデータごとに1テストのサイクルを回す。そのサイクルの中には、テストオブジェクトの生成やsetUptearDown、レポーティングなどさまざまな処理がある。ひとつのメソッドの中でforループするテストは、そういうオーバヘッドがないぶん早くなる。


forとdataProviderは置き換え可能というわけではない

dataProviderはデータごとに独立したテストになるので、ひとつ前のデータがFAILになっても次のデータのテストは実行される。一方、forでループした場合は、FAILになったデータでテストが止まる。こういう違いがあるので、両者は置き換え可能というわけでない点は注意する。


結論


  • 遅いと言っても、前後のデータにテスト実行計画が依存しないdataProviderのほうが普通はいい。

  • ただ、沢山のパターン(数万パターン等)を網羅してテストしたい場合は、dataProviderだと体感的につらくなるので、そういう場合はforを使ってもいいかもしれない。