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


目的

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

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

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

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

手順

phpunit.xmlで試験の結果をjunit形式で出力する

phpunitのマニュアルによるとphpunit.xmlにtype="junit"を指定するとテスト実行時に自動でログを出力してくれるので追記します。
今回はtests配下にlogというフォルダを設けてそちらに出力します。

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
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>

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

見やすい形式に変換する

こちらのXMLTテンプレートをもとにXMLをHTMLに変換するとかなり見やすくなるようですので、変換してみます。

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

sudo apt-get install xsltproc

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

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

こちらが出力されたHTMLです、かなり見やすくなったのではないでしょうか。

ただ、そのまま使うだけだとテストケースが出力されないため試験の内容がわかりにくく感じたのでテンプレートの内容を少し修正しました。

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

実例

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

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

php artisan make:auth

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

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'],
        ]);
    }
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' => '[email protected]',
            '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' => '[email protected]',
            '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への変換処理をかけます。

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

生成された結果がこちらです。
データセットを持つ試験項目はデータラベルで、単一の試験項目については関数名でレビュアーにどのような試験を実施する想定かを伝えることができそうです!

終わりに

上記のような自動レポートの機能を使って単体試験表を別途書かなくてもOKとなればいいなーと思います。あと変換の処理も合わせて自動でできそうな気もするのでそちらも別途検討してみようと思います。
PHPUnitのテストランナーを拡張して自動化できましたのでつづきを書きました

参考

PHPUnit のテスト結果を人間に優しい感じで出力する

自動化対象のユニットテスト(単体テスト)の仕様書を書くことは完全なる無駄である