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を使ってもいいかもしれない。