phpunitで作成したテストケースをもとに試験項目表を作成する

# 目的
私が以前所属していたチームでは
1. 単体試験表を作成（EXCELなどのドキュメント）
2. ユニットテストの作成
3. 実装
4. テスト実施

という流れで開発を行っていましたが、単体試験の試験表を作成と保守していくコストが掛かるようになり

1. ユニットコードの作成
2. 試験項目表の自動生成
3. 実装
4. テスト実施

とすることはできないか検討してみました。

# 手順
## phpunit.xmlで試験の結果をjunit形式で出力する
[phpunitのマニュアル](https://phpunit.readthedocs.io/ja/latest/configuration.html)によるとphpunit.xmlにtype="junit"を指定するとテスト実行時に自動でログを出力してくれるので追記します。
今回はtests配下にlogというフォルダを設けてそちらに出力します。

```xml:phpunit.xml
    <logging>
        <log type="junit" target="./tests/log/logfile.xml"/>
    </logging>
```

試しに実行してみます

```
vendor/bin/phpunit
PHPUnit 7.5.13 by Sebastian Bergmann and contributors.

...                                                                 3 / 3 (100%)

Time: 1.33 seconds, Memory: 14.00 MB
```

```xml:logfile.xml
<testsuites>
<testsuite name="" tests="3" assertions="3" errors="0" failures="0" skipped="0" time="1.102644">
<testsuite name="Unit" tests="2" assertions="2" errors="0" failures="0" skipped="0" time="0.806409">
<testsuite name="Tests\Unit\Auth\RegisterControllerTest" file="/var/www/html/tests/Unit/Auth/RegisterControllerTest.php" tests="1" assertions="1" errors="0" failures="0" skipped="0" time="0.768928">
<testcase name="testExample" class="Tests\Unit\Auth\RegisterControllerTest" classname="Tests.Unit.Auth.RegisterControllerTest" file="/var/www/html/tests/Unit/Auth/RegisterControllerTest.php" line="16" assertions="1" time="0.768928"/>
</testsuite>
<testsuite name="Tests\Unit\ExampleTest" file="/var/www/html/tests/Unit/ExampleTest.php" tests="1" assertions="1" errors="0" failures="0" skipped="0" time="0.037481">
<testcase name="testBasicTest" class="Tests\Unit\ExampleTest" classname="Tests.Unit.ExampleTest" file="/var/www/html/tests/Unit/ExampleTest.php" line="15" assertions="1" time="0.037481"/>
</testsuite>
</testsuite>
<testsuite name="Feature" tests="1" assertions="1" errors="0" failures="0" skipped="0" time="0.296235">
<testsuite name="Tests\Feature\ExampleTest" file="/var/www/html/tests/Feature/ExampleTest.php" tests="1" assertions="1" errors="0" failures="0" skipped="0" time="0.296235">
<testcase name="testBasicTest" class="Tests\Feature\ExampleTest" classname="Tests.Feature.ExampleTest" file="/var/www/html/tests/Feature/ExampleTest.php" line="15" assertions="1" time="0.296235"/>
</testsuite>
</testsuite>
</testsuite>
</testsuites>
```

...かなりわかりにくいですがひとまず試験の内容を出力することができました。

# 見やすい形式に変換する
[こちら](https://gist.github.com/jrfnl/3c28ea6d9b07fd48656d)のXMLTテンプレートをもとにXMLをHTMLに変換するとかなり見やすくなるようですので、変換してみます。

※xsltprocが入っていない場合は以下のコマンドでインストールしてください。

```
sudo apt-get install xsltproc
```

こちらがHTML変換用のコマンドです。

```
xsltproc phpunit.xslt tests/log/logfile.xml > output.html
```

こちらが出力されたHTMLです、かなり見やすくなったのではないでしょうか。
![output_html.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431797/0fa03f19-dc61-6982-b023-91d2292019b2.png)


ただ、そのまま使うだけだとテストケースが出力されないため試験の内容がわかりにくく感じたのでテンプレートの内容を少し修正しました。
![2019-08-02_13h03_32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431797/b77d6225-2852-eb75-e371-d8511b882aaa.png)


修正したphpunit.xsltはこちらにも配置してあるので好きに使ってください。
https://gist.github.com/yamamoto-taku/aa3cf2fe6d498a5a855c0f948a29cecd



## 実例
sampleとしてlaravelの会員登録フォームのバリデーションについてユニットテストを作成し、どのような感じになるか試してみます。

まず以下のコマンドでlaravelの会員機能一式を作成します。

```
php artisan make:auth
```

上記のコマンドで作成された以下の関数を試験するテストコードを作成しようと思います。

```php:RegisterController.php
    /**
     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    {
        return Validator::make($data, [
            //デフォルトのバリデーションだとひらがなでも登録できるので末尾の正規表現は追記しています。
            'name' => ['required', 'string', 'max:255', 'regex:/^[a-zA-Z0-9-_]+$/'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'password' => ['required', 'string', 'min:8', 'confirmed'],
        ]);
    }
```

```php:RegisterControllerTest.php

<?php

namespace Tests\Unit\Auth;

use App\Http\Controllers\Auth\RegisterController;
use Tests\TestCase;

class RegisterControllerTest extends TestCase
{
    protected $target;

    /**
     * @return void
     */
    public function setUp(): void
    {
        parent::setUp();
        $this->target = new RegisterController();
    }

    /**
     * @test
     * @throws \ReflectionException
     */
    public function 正常系()
    {
        $method = $this->changeAccessible('validator');

        $input = [
            'name' => 'yamamoto-taku',
            'email' => 'hoge@example.com',
            'password' => 'password',
            'password_confirmation' => 'password',
        ];
        $validator = $method->invoke($this->target, $input);
        $this->assertTrue($validator->passes());
    }

    /**
     * @param array $input
     * @dataProvider validatorProviderForFailure
     * @test
     * @throws \ReflectionException
     */
    public function 異常系(array $input)
    {
        $method = $this->changeAccessible('validator');
        $validator = $method->invoke($this->target, $input);
        $this->assertFalse($validator->passes());
    }

    /**
     * @return array
     */
    public function validatorProviderForFailure()
    {
        $input = [
            'name' => 'あいうえお',
            'email' => 'hoge@example.com',
            'password' => 'password',
            'password_confirmation' => 'password',
        ];
        $null = [
            'name' => null,
            'email' => null,
            'password' => null,
            'password_confirmation' => null,
        ];

        return [
            '必須入力チェック' => [$null],
            '日本語入力ひらがな' => [$input],
        ];
    }


    /**
     * @param $object
     * @return mixed
     * @throws \ReflectionException
     */
    public function changeAccessible($methodName)
    {
        $reflection = new \ReflectionClass($this->target);
        $method = $reflection->getMethod($methodName);
        $method->setAccessible(true);
        return $method;
    }
}

```

phpunitを実行してHTMLへの変換処理をかけます。


```shell
vendor/bin/phpunit tests/Unit/Auth/RegisterControllerTest.php
xsltproc phpunit.xslt tests/log/logfile.xml > output.html
```

生成された結果がこちらです。
データセットを持つ試験項目はデータラベルで、単一の試験項目については関数名でレビュアーにどのような試験を実施する想定かを伝えることができそうです！
![2019-08-02_15h32_51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431797/397c8383-7207-ae94-b12a-779b5aa8a564.png)

#終わりに
上記のような自動レポートの機能を使って単体試験表を別途書かなくてもOKとなればいいなーと思います。~~あと変換の処理も合わせて自動でできそうな気もするのでそちらも別途検討してみようと思います。~~ 
→[PHPUnitのテストランナーを拡張して自動化できましたのでつづきを書きました](https://qiita.com/yamamoto-taku/items/36a5a9a4fb54d2ce4055)

#参考
[PHPUnit のテスト結果を人間に優しい感じで出力する](https://blogs.technet.microsoft.com/livedevopsinjapan/2016/01/24/66/)

[自動化対象のユニットテスト（単体テスト）の仕様書を書くことは完全なる無駄である](https://inokara.hateblo.jp/entry/2017/09/17/223025)