PHPのテストで本当にdataProviderを使う必要があるのだろうか
PHPUnitにはdataProvider
という機能がある。
パラメタライズドテスト、またはテーブルドリブン(駆動)テストで書くための仕組み。
僕はずっとこの機能が使いづらくて、普通にforeachで回す方が良いのではと思っている。
今日たまたまdataProvider
を推奨している記事を続けて目にしたので、改めて考えを整理しておく。
dataProviderのメリット
foreachと比較したdataProvider
のメリットはこちらの記事が分かりやすかったので引用する。
PHPUnitとデータプロバイダとテストケース生成
メリット1: setUp/tearDownが使える
確かにモックを使うテストならその方が助かる。
メリット2: どのデータでテストが落ちたのか分かりやすい
確かにforeachだとちょっと面倒。Goでテストを書いている時にt.Error()
をいちいち書いていく感覚に似てる。
メリット3: 例外のテストとの組み合わせ
確かに例外が発生した段階で止まっちゃうと面倒。
dataProviderの(個人的)デメリット
上記のメリットはどれも同意するけど、個人的にはそれ以上にデメリットを感じてしまう。
それはテストの本体とdataProvider
のメソッドが離れていること。ロジックとデータは近いところにある方が読みやすいと思うけど好みなのかもしれない。
例えばPHPUnitのリポジトリにあるこのテストコード。
https://github.com/sebastianbergmann/phpunit/blob/cca308e970eebb1f21cd702071b09bfd40a1c705/tests/unit/Util/RegularExpressionTest.php
testValidRegex
のテストを読もうとするvalidRegexpProvider
の定義を見る
という視線の移動と記憶が必要になる。
これなら↓のようにテスト本体とデータは一緒にあった方が個人的には読みやすい。
<?php public function testValidRegex(): void { foreach([ ['#valid regexp#', 'valid regexp', 1], [';val.*xp;', 'valid regexp', 1], ['/val.*xp/i', 'VALID REGEXP', 1], ['/a val.*p/', 'valid regexp', 0], ] as $case) { $this->assertEquals($case[2], RegularExpression::safeMatch($case[1], $case[0])); } }
(エラーメッセージとか一時変数に置くとかは省いている)
もちろんdataProvider
の方が適切というケースもあると思うけど、同様に普通にforeachの方が良いケースもあると思う。何でもかんでもdataProvider
を使うのは違う気がする。
このテストとか冗長なだけなのでは。
ただ「使うかどうかは個人の判断」にしちゃうとテストの書き方に統一性がなくなるから、プロジェクトとしては「必ず使う」でルール化した方がいいのかもしれない。
また個人的に読みにくいと感じる理由の一つは、dataProvider
側だけを見ても値の意図が分からないから。
<?php public function canonicalizeProvider(): array { return [ ['{"name":"John","age":"35"}', '{"age":"35","name":"John"}', false], ['{"name":"John","age":"35","kids":[{"name":"Petr","age":"5"}]}', '{"age":"35","kids":[{"age":"5","name":"Petr"}],"name":"John"}', false], ['"name":"John","age":"35"}', '{"age":"35","name":"John"}', true], ]; }
これはphpunit/tests/unit/Util/JsonTest.phpから拝借した。
それぞれの値の意図は、ここを見ただけでは分からない。
テストコード側の引数を見て初めて理解する。
<?php public function testCanonicalize($actual, $expected, $expectError): void
テストコードとデータを行き来しないと読み解けない。これが僕にはつらい。
結局どっちが良いのか
最初に引用したdataProvider
のメリットのうち2つは、「テストが落ちた時のメンテ性を上げる」ためのものだった。
僕があげたdataProvider
のデメリットは「他人がコードを読む時」を想定したものだった。
つまり優劣というより重要視する観点が違うんだと思う。僕は可読性を大事にしている。でも「dataProvider
の方が見やすい。foreachの方が見づらい」って人もいるだろうから、そうなるとforeachの方が良い理由はほとんどないのかな。