2020-09-15

リモートホストで動くPHPアプリケーションに対するE2Eテストでカバレッジを測定する方法

リモートホストで動くPHPアプリに対してE2Eテストをしたときのカバレッジを計測する方法を紹介します。

なお、こちらの記事を参考にしています(というかほぼ同じです)

PHP Code Coverage for your web/selenium automation · Tech Adventures by Tarun Lalwani

原理としてはリモートホスト上のスクリプトが実行する前に xdebug_start_code_coverage() などのカバレッジ計測の関数を呼び出し スクリプトが終了する前に xdebug_stop_code_coverage() xdebug_get_code_coverage() を呼び出してカバレッジを別ファイルに保存。 保存されたカバレッジファイルを集計してカバレッジのHTMLファイルを吐き出します。

1. スクリプトが実行する前にxdebug_start_code_coverageを呼び出す

PHPにはauto_prepend_fileという事前に呼び出すスクリプトを指定する仕組みがあります。

具体的にはphp.iniやapache/nginxの設定ファイルに以下のような記述をすることで
指定したファイルがrequireされた状態でリクエストされたPHPファイルが呼び出されることになります。

php.ini

auto_prepend_file=/path/to/start.php

apache

php_value auto_prepend_file "/path/to/start.php"

nginx

fastcgi_param PHP_VALUE "auto_prepend_file=\"/path/to/start.php\"";

事前に呼び出すスクリプトファイルはこちらです。

<?php

xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);

class CoverageDumper
{
    private string $testName;

    function __construct(string $testName)
    {
        $this->testName = $testName;
    }

    function __destruct()
    {
        $coverageName = '/path/to/coverage/coverage-' . $this->testName . '-' . microtime(true);
        try {
            xdebug_stop_code_coverage(false);
            $codecoverageData = json_encode(xdebug_get_code_coverage());
            file_put_contents($coverageName . '.json', $codecoverageData);
        } catch (Exception $ex) {
            file_put_contents($coverageName . '.ex', $ex);
        }
    }
}

$testName = (isset($_COOKIE['test_name']) && !empty($_COOKIE['test_name'])) ? $_COOKIE['test_name'] : 'unknown_test_' . time();
$_coverageDumper = new CoverageDumper($testName);

コード計測にはxdebugを使うのでリモートホスト先で有効化する必要があります。 最初に xdebug_start_code_coverage() でカバレッジ計測を開始し CoverageDumperのデストラクタに xdebug_stop_code_coverage() xdebug_get_code_coverage() を仕込むことで 呼び出し先のスクリプトが終了したときにカバレッジを書き出してくれるようになります。

2. カバレッジを集計する

上記スクリプトによって生成されたカバレッジファイルは xdebug_get_code_coverage() をjson化したものなので 静的ファイルを書き出すにはひと手間必要です。

また、1リクエストあたり1つのカバレッジファイルが作成されるため、集計処理も必要です。

これらは phpunit/php-code-coverage を使うと 簡単に複数のカバレッジファイルから1つのカバレッジページを作成することができます。

<?php

require_once 'vendor/autoload.php';

$coverages = glob('./coverage/coverage-*.json');

$finalCoverage = new SebastianBergmann\CodeCoverage\CodeCoverage();
$finalCoverage->filter()->addDirectoryToWhitelist("/path/to/app");

$count = count($coverages);
foreach ($coverages as $index => $coverageFile)
{
    echo 'Processing coverage (' . (string)($index + 1) . "/$count) from $coverageFile". PHP_EOL;
    $codecoverageData = json_decode(file_get_contents($coverageFile), JSON_OBJECT_AS_ARRAY);
    $testName = str_ireplace(basename($coverageFile,".json"),"coverage-", "");
    $finalCoverage->append($codecoverageData, $testName);
}

echo "Generating final report..." . PHP_EOL;
$report = new \SebastianBergmann\CodeCoverage\Report\Html\Facade();
$report->process($finalCoverage, 'reports');
echo "Report generated succesfully". PHP_EOL;

こちらでも、xdebugが必要になるので集計するホスト側でもxdebugも有効化する必要があります。

これでE2Eテスト時にもカバレッジを正確に計測することができます。

余談

shadow proxyとかで上記のカバレッジ集計の仕組みを仕込んだアプリに対して本番同等のリクエストを流してカバレッジを集計すると、不要な関数・メソッド・行を炙り出せるのかも?

このエントリーをはてなブックマークに追加