はじめに
私が担当しているphp(codeigniter)のプロダクトでは機能改修を行ったらテスト仕様書を作成し、手動テストを行っていますが自分で作成したテストケースに不安を感じます...
そんな時にコードカバレッジ計測が役に立つと思い、導入してみました。
結果として、テスト仕様書の漏れを事前に防ぐことができ、本番反映時の精神的なストレスが軽減されたと感じてます☺️
対象の方
以下の条件に当てはまる方は、参考になるのではないかと思います!
💡phpを使って開発している人
💡自動テストを作成する工数を確保できない人
💡自分で考えたテストケースの漏れを防ぎたい人
💡(画面ぽちぽち)手動テストしたとき、プログラムのどこを通過したか視覚的にわかりたい人
💡コードカバレッジ導入のハードルが高いと感じている人
概要
手動テストを行っているphp環境のプロダクトにコードカバレッジを導入します。
xdebugでコードカバレッジ有効化モードにして、phpunitのphp-code-coverageでコードカバレッジ計測・レポート出力を行っていきます。
以下、簡易的な流れです。
導入編
開発環境
xdebug導入済
php 8.2
codeigniter
codeigniterの簡易版ディレクトリ構造
codeigniter
├── composer.json
├── application
├── htdocs
└── coverage_report(コードカバレッジ計測結果を置いておくフォルダ)
・
└── php_scripts(コードカバレッジ計測するファイルを置いておくフォルダ)
├── coverage-data
├── append.php
├── prepend.php
└── report.php
laravelや他のphpフレームワークに導入する際は、php_scripts/*.php
ファイルのディレクトリに関するプログラムを修正すれば利用可能です。
手動テストの流れ
普段作成するテスト仕様書に沿って、手動でテストを実行します。
テストがすべて完了したら、最後にreport.php
を実行してカバレッジレポートを出力し、作業完了となります。
出力レポートのサンプル
各ファイルの詳細
composer.json
コードカバレッジを行うライブラリをインストールします。
phpunitをComposerで管理している場合は、phpunit/php-code-coverageは導入済なので、個別にインストールする必要はありません。
composer require --dev phpunit/php-code-coverage
php.ini
コードカバレッジを行うための設定をphp.iniへ記述します。
; /var/www/codeigniterはベースディレクトリです。
[Coverage]
; コードカバレッジ計測を有効にします。
xdebug.mode=coverage
; auto_prepend_fileは、リクエストされた.phpファイルよりも前に、読み込まれるファイルです。
auto_prepend_file="/var/www/codeigniter/php_scripts/prepend.php"
; auto_append_fileは、リクエストされた.phpファイルの最後の処理が完了した後に、読み込まれるファイルです。
auto_append_file="/var/www/codeigniter/php_scripts/append.php"
prepend.php
アプリのコードカバレッジ計測を開始し、対象の.phpファイルを登録します。
計測オブジェクトを$GLOBALS['__coverage__']
に保存し、あとで結果を取得できるようにします。
<?php
require_once __DIR__ . '/../vendor/autoload.php';
use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Driver\Selector;
use SebastianBergmann\CodeCoverage\Filter;
use SebastianBergmann\CodeCoverage\NoCodeCoverageDriverAvailableException;
try {
$filter = new Filter();
foreach (['application'] as $dir_name) {
$dir = dirname(__DIR__) . '/' . $dir_name;
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
foreach ($files as $file) {
if ($file->isFile() && $file->getExtension() === 'php') {
$path = realpath($file->getPathname());
if ($path) {
$filter->includeFile($path);
}
}
}
}
// コンテナ起動時に例外が発生するので、try-catchで囲む
$driver = (new Selector())->forLineCoverage($filter);
$coverage = new CodeCoverage($driver, $filter);
$coverage->start('manual_' . uniqid());
$GLOBALS['__coverage__'] = $coverage;
} catch (NoCodeCoverageDriverAvailableException $e) {
error_log($e->getMessage());
}
append.php
アプリのコードカバレッジ計測を終了し、結果を .cov ファイルとして保存します。
$GLOBALS['__coverage__']
に保存されていたデータをphp_scripts/coverage-data
に出力します。
<?php
if (isset($GLOBALS['__coverage__'])) {
$coverage = $GLOBALS['__coverage__'];
$coverage->stop();
$dir = __DIR__ . '/coverage-data';
if (!is_dir($dir)) {
if (!mkdir($dir, 0777, true)) {
throw new RuntimeException("Failed to create directory: $dir");
}
}
$file = $dir . '/' . uniqid('cov_') . '.cov';
$result = file_put_contents($file, serialize($coverage));
if ($result === false) {
error_log("Failed to write coverage data to file: $file");
throw new RuntimeException("Unable to write coverage data to file: $file");
}
}
report.php
.cov ファイルに保存されたカバレッジ結果をすべて読み込み、1つにまとめて HTML レポートを出力します。
出力されたcoverage_report/index.html
を開くと、テストで実行されたコードがブラウザで確認できます。
<?php
require_once __DIR__ . '/../vendor/autoload.php';
use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\Report\Html\Facade;
// 検証時に作成されるカバレッジデータを削除(デフォルトは削除しない)
$is_deleted = false;
$coverage = null;
$dir = __DIR__ . '/coverage-data';
foreach (glob($dir . '/*.cov') as $file) {
$data = unserialize(file_get_contents($file));
if ($data instanceof CodeCoverage) {
if ($coverage === null) {
$coverage = $data;
} else {
$coverage->merge($data);
}
}
}
if ($coverage !== null) {
// カバレッジデータのレポート出力 - http://localhost/coverage_report/index.html にアクセスすれば表示
$writer = new Facade();
$writer->process($coverage, __DIR__ . '/../htdocs/coverage_report');
echo "Report generated at " . realpath(__DIR__ . '/../htdocs/coverage_report') . "/\n";
if ($is_deleted) {
foreach (glob($dir . '/*.cov') as $file) {
unlink($file);
}
}
} else {
echo "No valid coverage data found.\n";
}
report.php実行後、htdocs/coverage_report
ディレクトリに大量のファイルが生成され、index.htmlを開くとレポートが確認できます。
codeigniter
└── htdocs
└── coverage_report(コードカバレッジ計測結果を置いておくフォルダ)
・
・
└──index.html
最後に
自動テストをまだ導入できず、手動テストに頼っている現場はまだまだ存在すると思ってます。本記事が、そんな方々の一助になれば幸いです。
私の担当しているプロダクトも、自動テスト環境を整えるべく現在スケジュールを組んでいるところですが、移行期間中でもコードカバレッジを取り入れたことで、テスト漏れの不安が大きく減りました。
「手動テストしかできないから……」と悩んでいる方は、まずはコードカバレッジだけでも試してみてください。テストの安心感がぐっと高まります☺️