この記事はAll About Group(株式会社オールアバウト) Advent Calendar 2022 5日目の記事です。
今時はテストを書くのは当たり前になっていると思います。PHPUnitでちょっと便利なアノテーションがあるので紹介します。
@testWith
アノテーション
1つのメソッドに対していろいろなデータパターンを検証したいことがあると思います。
そういうときに素直にテストケースを書くとこんなふうに冗長になってしまいます。
use PHPUnit\Framework\TestCase;
class DollarTest extends TestCase
{
/** @test */
public function times__5ドルを2倍すると10ドルである()
{
$dollar = new Dollar(5);
$this->assertEquals(new Dollar(10), $dollar->times(2));
}
/** @test */
public function times__15ドルを3倍すると45ドルである()
{
$dollar = new Dollar(15);
$this->assertEquals(new Dollar(45), $dollar->times(3));
}
}
これだとデータパターンを追加するたびにテストケースをコピーすることになって読みにくいテストになります。
メジャーな解決手段だとデータプロバイダーを使って書くことが思いつきます。
use PHPUnit\Framework\TestCase;
class DollarTest extends TestCase
{
/**
* @test
* @dataProvider provider
*/
public function times__dollarにmultiplicationを掛けた金額のDollarオブジェクトを返す($dollar, $multiplication, $expect)
{
$dollar = new Dollar($dollar);
$this->assertEquals(new Dollar($expect), $dollar->times($multiplication));
}
/** @test */
public function provider(): array
{
return [
// $dollar, $multiplication, $expect
[5, 2, 10],
[15, 3, 45],
];
}
}
これだとデータパターンを追加することになったらprovider
メソッドにパターンを追加したら良いですが、テストケースとprovider
メソッドが離れているのでテストケースとprovider
メソッドがが増えてくると管理や読むのが大変になってしまいます。
そこで@testWith
アノテーションの出番です。@testWith
アノテーションはその名の通りテストケースのDocコメントでデータを定義できます。
use PHPUnit\Framework\TestCase;
class DollarTest extends TestCase
{
/**
* @test
* @testWith [5, 2, 10]
* [15, 3, 45]
*/
public function times__dollarにmultiplicationを掛けた金額のDollarオブジェクトを返す($dollar, $multiplication, $expect)
{
$dollar = new Dollar($dollar);
$this->assertEquals(new Dollar($expect), $dollar->times($multiplication));
}
}
テストケースを配列の形で1行ずつ書いていくことでデータパターンを追加できます。これだとテストケースとデータが近くにあるのでテストとパターンを見た時にパッとわかりやすいですし、どのテストケースのパターンなのかもわかりやすくなります。
dataProvider
かtestWith
のどちらが良いかということについては、補足記事を書きましたのでそちらも合わせて読んでください。
@testdox
アノテーション
先ほどのサンプルのテストケースではこのように書いてました。
public function times__dollarにmultiplicationを掛けた金額のDollarオブジェクトを返す($dollar, $multiplication, $expect)
読めばなんとかわかりますが、アンダースコアでつなげて書いていていたり、処理をそのまま書いているので意図が分かりにくかったりします。
@testdox
アノテーションを使うとテストケースの説明を定義することができます。
/**
* @test
* @testdox times: 掛け算をして新しいDollarオブジェクトを返す
* @testWith [5, 2, 10]
* [15, 3, 45]
*/
public function times__dollarにmultiplicationを掛けた金額のDollarオブジェクトを返す($dollar, $multiplication, $expect)
{
$dollar = new Dollar($dollar);
$this->assertEquals(new Dollar($expect), $dollar->times($multiplication));
}
ここで設定した値はテストの実行結果にも出てくるので、どういうことができるのかを書くことでテストケース名の冗長さから(多少)開放されることと思います。
# --testdox のオプションをつけるとテストケース名が表示される
$ ./vendor/bin/phpunit --testdox
PHPUnit 9.5.26 by Sebastian Bergmann and contributors.
Runtime: PHP 8.1.12
Configuration: /Users/watabe/Documents/code/php-tdd/phpunit.xml
Dollar
✔ times: 掛け算をした結果を新しいDollarオブジェクトとして返す with data set 0 4 ms
✔ times: 掛け算をした結果を新しいDollarオブジェクトとして返す with data set 1 1 ms
Time: 00:00.005, Memory: 6.00 MB
OK (2 tests, 2 assertions)
$ ./vendor/bin/phpunit --testdox
PHPUnit 9.5.26 by Sebastian Bergmann and contributors.
Runtime: PHP 8.1.12
Configuration: /Users/watabe/Documents/code/php-tdd/phpunit.xml
Dollar
✘ times: 掛け算をした結果を新しいDollarオブジェクトとして返す with data set 0 5 ms
┐
├ Failed asserting that two objects are equal.
┊ ---·Expected
┊ +++·Actual
┊ @@ @@
┊ App\Dollar Object (
┊ -····'amount'·=>·10
┊ +····'amount'·=>·5
┊ )
│
╵ /Users/watabe/Documents/code/php-tdd/tests/DollerTest.php:22
┴
もう1つこのアノテーションを使うことのメリットは、あくまでDocコメントなので、PHPの関数名に使えない文字も書くことができます。たとえば先のテストケースですが、このように書くとsyntax errorになってしまいます。
public function times__$dollarに$multiplicationを掛けた金額のDollarオブジェクトを返す($dollar, $multiplication, $expect)
上で書いたように$
を抜いて書くことでも通じますが@testdox
アノテーションにこのように書くことができます(なんならスペースを入れたりもできます)
/**
* @test
* @testdox times: $dollarに$multiplicationを掛けた金額のDollarオブジェクトを返す
*/
テストコードもちゃんとメンテナンスすることでプロダクトコードを読むときの手がかりになったりするので、こういう用意された機構を活用しましょう。