アノテーションを使ってPHPUnitのテストをすっきりさせる

この記事はAll About Group（株式会社オールアバウト） Advent Calendar 2022 5日目の記事です。

今時はテストを書くのは当たり前になっていると思います。PHPUnitでちょっと便利なアノテーションがあるので紹介します。

## `@testWith`アノテーション

1つのメソッドに対していろいろなデータパターンを検証したいことがあると思います。
そういうときに素直にテストケースを書くとこんなふうに冗長になってしまいます。

```php:DollarTest.php
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));
    }
}
```

これだとデータパターンを追加するたびにテストケースをコピーすることになって読みにくいテストになります。

メジャーな解決手段だとデータプロバイダーを使って書くことが思いつきます。

```php:DollarTest.php
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コメントでデータを定義できます。

```php:DollarTest.php
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`のどちらが良いかということについては、[補足記事](https://qiita.com/wantonbe/items/895f54def78a958d7888)を書きましたのでそちらも合わせて読んでください。

## `@testdox`アノテーション

先ほどのサンプルのテストケースではこのように書いてました。

```php
public function times__dollarにmultiplicationを掛けた金額のDollarオブジェクトを返す($dollar, $multiplication, $expect)
```

読めばなんとかわかりますが、アンダースコアでつなげて書いていていたり、処理をそのまま書いているので意図が分かりにくかったりします。
`@testdox`アノテーションを使うとテストケースの説明を定義することができます。

```php
    /**
     * @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));
    }
```

ここで設定した値はテストの実行結果にも出てくるので、どういうことができるのかを書くことでテストケース名の冗長さから（多少）開放されることと思います。

```bash:PHPUnit実行結果（成功した場合）
# --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)
```

```bash:PHPUnit実行結果（失敗した場合）
$ ./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になってしまいます。

```php:メソッド名に記号を使えないのでsyntax errorになる
public function times__$dollarに$multiplicationを掛けた金額のDollarオブジェクトを返す($dollar, $multiplication, $expect)
```

上で書いたように`$`を抜いて書くことでも通じますが`@testdox`アノテーションにこのように書くことができます（なんならスペースを入れたりもできます）

```php:testdoxを使った例
/**
 * @test
 * @testdox times: $dollarに$multiplicationを掛けた金額のDollarオブジェクトを返す
 */
```

テストコードもちゃんとメンテナンスすることでプロダクトコードを読むときの手がかりになったりするので、こういう用意された機構を活用しましょう。