【PHPUnit】複数あるassertのうちどれかが失敗しても全て実行させる方法

# ここで書くこと
この記事では、

- 複数のテストパターン（テストで使う値）を用いてテストしたいが、一つのアサーションが落ちた時に他のテストパターンの結果が見れず困っている
- テストコードとテストパターンを分離する方法が知りたい
- データプロバイダを使用してテストメソッドにテストパターンを渡したいと考えているがその方法が分からない

という方向けに、テストメソッド内に複数あるアサーションのうちのどれかが失敗しても全て実行させる方法の一つとして、「データプロバイダを使ったテストのやり方」について記述しています。

### 執筆環境
執筆時点の環境は
OS: macOS Catalina
PHP: PHP 7.3.11
PHPUnit: PHPUnit 9.5.4
です（2021.06.06時点）。

# PHPUnit及びアサーションメソッドについて
データプロバイダの説明に入る前に、関連用語としてPHPUnitとアサーションメソッドについて少し触れたいと思います。

PHPUnitとはPHPにおける単体テストツールで、テストを行うための様々な機能やアサーションメソッドが用意されています。アサーションメソッドの代表的なものに

```
assertSame($actual, $expected)
```
があります。このアサーションメソッドは2つの引数が値・型ともに同一であるかを比較し、異なる場合にエラーを報告します。

アサーションメソッドには目的に応じて様々な種類があります：
[PHPUnitの主なAssertメソッド一覧](https://qiita.com/rev84/items/12fbd16d210d6a86eff9)

アサーションメソッドは以下のtestCalcSquare()のように、テストメソッドにおいてテスト対象のメソッドにテストパターンを渡して目的の結果が返ってくるかを判定する際に用いられます。

```Square.php
<?php
class Square
{
    // 引数を二乗した値を返す
    function calcSquare($a)
    {
        return $a * $a;
    }
}
```
```SquareTest.php
<?php
use PHPUnit\Framework\TestCase;
include('Square.php');

class SquareTest extends TestCase
{
    // calcSquare()をテストするメソッド
    public function testCalcSquare()
    {
        $square = new Square();
        $this::assertSame($square::calcSquare(5), 25);
    }
}
```

テストパターンが複数あるなどテストメソッド内でアサーションメソッドを複数記述した時に、途中で一つでも結果がfailureとなるとそこでテストが終了し、以降のアサーションメソッドは実行されません。

またテストパターンが増えるとその数だけアサーションメソッドを書く必要があり、テストコードが長くなって可読性が悪くなってしまうことも考えられます。

テストパターンが複数あり、（いずれかのパターンでテストが落ちても）一度のテストで全てのテストパターンの結果が分かるようにしたい場合、またテストパターンが増えた時にテストコードが冗長になってしまうことを防ぎたい場合は、今回ご紹介する「データプロバイダ」を用いる方法が有効です。

# データプロバイダについて
データプロバイダはPHPUnit上で用意されている機能の一つで、これを使うことでテストパターンとテストコードを分離することができます。

大まかなイメージとしては、テストパターンのみを持つメソッドからテストパターンを1つ渡してテストメソッドを実行し、それを各テストパターンごとに繰り返し行う、というような流れで処理が行われます。

テストパターンのみを持つメソッドを新たに作成し、テストメソッドにてデータプロバイダ用のアノテーション（```@dataProvider```）を用いてそのメソッドを指定することでデータプロバイダ機能を使うことができます。

公式ドキュメント：
[2.PHPUnit 用のテストの書き方](https://phpunit.readthedocs.io/ja/latest/writing-tests-for-phpunit.html)

具体的なやり方についてはこの後説明していきたいと思います。

# データプロバイダを使ったテストのやり方
先ほどの例を使って、データプロバイダ機能の使い方について書いていきます。

まず、以下のようにテストコードからテストパターン部分を取り出して、テストパターンを返すメソッドを作成します（ここでは例として3パターンを用意しています）。アサーションメソッドに渡していた引数を要素に持つ配列が1つのテストパターンとなります。

```SquareTest.php
// テストパターンを返すメソッドを新たに作る
public function providerForTestCalcSquare()
{
    return [
        [5, 25],
        [10, 100],
        [15, 225],
    ];
}
```
次に、テストパターンの値に対応するようにテストメソッドの引数を設定します。それに伴って、テストコード内のテストパターンの値も引数を使うように変更します。

```SquareTest.php
// テストパターン（今回の例では[5, 25]など）に対応するように引数を設定
public function testCalcSquare(int $num, int $expected)
{
    $square = new Square();
    // アサーションメソッドの引数もテストメソッドの引数を使うように変更
    $this::assertSame($square::calcSquare($num), $expected);
```
そして、データプロバイダ機能を使いたいテストメソッドにてPHPDocにデータプロバイダアノテーション（```@dataProvider```）を書き、テストパターンを返すメソッドを指定します。

```SquareTest.php
/**
 * @dataProvider providerForTestCalcSquare
 */
public function testCalcSquare(int $num, int $expected)
{
    ...
```

<br>
最終的に、テストコードは以下のようになります。

```SquareTest.php
<?php
use PHPUnit\Framework\TestCase;
include('Square.php');

class SquareTest extends TestCase
{
    public function providerForTestCalcSquare()
    {
        return [
            [5, 25],
            [10, 100],
            [15, 225],
        ];
    }

    /**
     * @dataProvider providerForTestCalcSquare
     */
    public function testCalcSquare(int $num, int $expected)
    {
        $square = new Square();
        $this::assertSame($square::calcSquare($num), $expected);
    }
}
```
これでデータプロバイダ機能を使ってテストコードが実行されます。なお、テストを実行する手順自体には変更はないので、ターミナル上で

```
$ phpunit SquareTest.php
```
のようにすればテストを実行できます。

データプロバイダを使う時の注意点として、

- データプロバイダメソッドはpublicでないといけない
- データプロバイダメソッドに特に命名規則は無いが、PHPUnitにおいてはtestで始まる名前のpublicなメソッドはテストメソッドとして認識されるので、testで始まる名前は避ける

が挙げられます。
<br>

また、テストパターンが少ない場合はPHPUnitのtestWith機能を使うやり方もあります。

testWith機能の使い方は、以下のようにテストメソッドにおいてPHPDocにtestWithアノテーション（```@testWith```）を書き、そこに渡したいテストパターンを直接書きます。

```SquareTest.php
/**
 * @testWith [5, 25]
 *           [10, 100]
 */
public function testCalcSquare(int $num, int $expected)
{
    $square = new Square();
    $this::assertSame($square::calcSquare($num), $expected);
}
```
このやり方でもデータプロバイダと同様に、いずれかのテストパターンが落ちても全てのテストパターンが実行されます。


# 参考にさせていただいたサイト

- [PHPUnitとデータプロバイダとテストケース生成](https://qiita.com/Hiraku/items/5c49987f4e9e167dad86)
- [PHPUnitのdataProviderを使ったらrisky testになった話](https://ver2.sssignal.com/column/phpunit_risky_dataProvider)
- [データプロバイダーを使用してテストコードをDRYに](https://shkn.hatenablog.com/entry/2019/04/19/213324)
- [PHPUnitのプラクティス](https://shkn.hatenablog.com/entry/2019/04/19/213324)