12
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

自動テストなしの現場にコードカバレッジを導入するお話【php/codeigniter】

Last updated at Posted at 2025-07-10

はじめに

私が担当している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を実行してカバレッジレポートを出力し、作業完了となります。

出力レポートのサンプル

image.png

各ファイルの詳細

composer.json

コードカバレッジを行うライブラリをインストールします。

phpunitをComposerで管理している場合は、phpunit/php-code-coverageは導入済なので、個別にインストールする必要はありません。

composer.json
composer require --dev phpunit/php-code-coverage

php.ini

コードカバレッジを行うための設定を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__']に保存し、あとで結果を取得できるようにします。

prepend.php
<?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に出力します。

append.php
<?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を開くと、テストで実行されたコードがブラウザで確認できます。

report.php
<?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

最後に

自動テストをまだ導入できず、手動テストに頼っている現場はまだまだ存在すると思ってます。本記事が、そんな方々の一助になれば幸いです。

私の担当しているプロダクトも、自動テスト環境を整えるべく現在スケジュールを組んでいるところですが、移行期間中でもコードカバレッジを取り入れたことで、テスト漏れの不安が大きく減りました。

「手動テストしかできないから……」と悩んでいる方は、まずはコードカバレッジだけでも試してみてください。テストの安心感がぐっと高まります☺️

12
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?