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


やりたいこと

前回phpunitで作成したテストケースをもとに試験項目表を作成するでテストケースから試験の内容を出力するところまで作成できました。

ただ、以下のような不満があります。

  • コマンドを2回実行しないと試験表が出力されない。
  • テストしたクラス全部の結果が一つにレポートにまとまってしまうので個別のテストクラスごとの結果も保存しておきたい。
    • ※製品のリポジトリとは別のリポジトリに管理しやすい形だと嬉しい

そこで今回はPHPUnitのテストランナーの拡張して試験あとに実行する処理を追加していこうと思います。

処理の全体像

イメージとしてはPHPUnitのテストランナーで実行されたテストクラスを監視して以下のような簡単なしくみを実装しています。

出来上がった試験結果はこんな感じでとりあえず配置しようと思います。

実装方法

テストランナー拡張用のクラスを用意する

phpunitの実行中に処理を挟むためのテストランナー拡張用クラスを用意します。
今回は以下のようなfileを配置します。配置場所はどこでもよいですが今回はtestsフォルダ配下にExtensionフォルダを設けてそちらに格納するようにしました。

BeforeTestHookで実行中のテストクラス名を取得して、処理完了後にHTMLへの変換処理を実行しています。

余談ですがAfterLastTestHookというイベントフックもあったのですが、HTMLへの変換のもとにしているファイル出力がAfterLastTestHookのタイミングではまだ生成されていなかったのでデストラクタでテスト完了後の処理を記述しています。

TestRunnerExtension.php
<?php


namespace Tests\Extension;

use PHPUnit\Runner\BeforeTestHook;

final class TestRunnerExtension implements BeforeTestHook
{
    /**
     * @var string
     */
    private $resultFilePath = 'tests/log/logfile.xml';

    /**
     * @var string
     */
    private $testedFileList = 'tests/log/testedFileList.txt';

    /**
     * @var string
     */
    private $resultDir = 'tests/log/result/';

    /**
     * @var array
     */
    private $classList = [];

    /**
     * PHPUnitのログが出力後にファイル生成を行いたいので
     * __destructで終了処理を記述する。
     */
    public function __destruct()
    {
        if (!file_exists($this->resultFilePath)) {
            return;
        }
        if (count($this->classList) > 1) {
            $this->multiClassTestReport();
        } else {
            $this->singleClassTestReport();
        }
    }

    /**
     * 複数のクラスにまたがって試験した場合は個別のテストクラスも実行する。
     * @throws \ReflectionException
     */
    private function multiClassTestReport()
    {
        exec('xsltproc phpunit.xslt ' . $this->resultFilePath . ' > ' . $this->resultDir . 'output.html');

        if (file_exists($this->testedFileList)) {
            unlink($this->testedFileList);
        }

        foreach ($this->classList as $className) {
            $reflection = new \ReflectionClass($className);
            exec('vendor/bin/phpunit ' . $reflection->getFileName());
        }
    }

    /**
     * 単一のクラスの試験ではクラス名と対応したパスにファイルを生成する。
     */
    private function singleClassTestReport()
    {
        $filePathArr = explode('\\', $this->classList[0]);
        $fileName = array_pop($filePathArr);
        $filePath = implode('/', $filePathArr);

        if (!file_exists($this->resultDir . $filePath)) {
            mkdir($this->resultDir . $filePath, 0777, true);
        }
        exec('xsltproc phpunit.xslt ' . $this->resultFilePath . ' > ' . $this->resultDir . $filePath . '/' . $fileName . '.html');
    }

    /**
     * 実行されたテスト情報からテスト対象のクラス名を取得する。
     * @param string $test
     */
    final public function executeBeforeTest(string $test): void
    {
        $test = substr($test, 0, strpos($test, '::'));
        if (!in_array($test, $this->classList, true)) {
            $this->classList[] = $test;
        }
    }
}

phpunit.xmlに拡張クラスを追記する。

上記で作成した拡張クラスをphpnit.xmlに以下のように追記します。
これでphpunitを実行するだけで拡張クラスに記述した処理を自動で実行してくれます。

phpunit.xml
    <extensions>
        <extension class="Tests\Extension\TestRunnerExtension"/>
    </extensions>