Appearance
PHPUnitテスト実行ガイド
目的
このドキュメントは、プロジェクトでPHPUnitを使用してテストを実行する方法を初心者向けに説明します。
対象読者
- テスト経験が少ない開発者
- このプロジェクトに新しく参加する開発者
- PHPUnitを初めて使用する開発者
PHPUnitとは
PHPUnitの役割と目的
PHPUnitは、PHPアプリケーションのユニットテストを実行するためのテストフレームワークです。このプロジェクトでは、以下の目的で使用しています:
- コードの品質保証: 実装したコードが期待通りに動作することを確認
- リファクタリングの安全性: コードを変更しても既存の機能が壊れていないことを確認
- バグの早期発見: 本番環境にデプロイする前に問題を発見
このプロジェクトでのテストの種類
このプロジェクトでは、以下の2種類のテストを用意しています:
ユニットテスト (
tests/Unit/)- 個別のクラスやメソッドの動作を検証
- 現在23個のテストファイルが存在
- 例:
DailyArticleResultServiceTest.php、DailyDataRepositoryBehaviorTest.phpなど
パフォーマンステスト (
tests/Performance/)- コードの実行速度やメモリ使用量を検証
- 例:
ConverterPerformanceTest.php(シングルトン化によるパフォーマンス向上を検証)
環境セットアップ
前提条件
- PHP: 8.3以上
- Composer: 依存関係管理ツール
依存関係のインストール方法
プロジェクトルートディレクトリで以下のコマンドを実行してください:
bash
# 開発依存関係を含めてインストール
composer install
# または、setupスクリプトを使用(依存関係インストール + レポートディレクトリ作成)
composer setupこれにより、PHPUnit 11.0以上とその他のテスト関連ツール(Brain Monkey、Mockeryなど)がインストールされます。
テスト実行方法
基本コマンド
すべてのテストを実行
bash
composer testまたは、PHPUnitを直接実行:
bash
vendor/bin/phpunitテストカバレッジレポートを生成
bash
composer test-coverageカバレッジレポートは reports/coverage/ ディレクトリにHTML形式で生成されます。ブラウザで reports/coverage/index.html を開くと、カバレッジレポートを確認できます。
特定のテストファイルを実行
特定のテストファイルのみを実行する場合:
bash
# 特定のテストファイルを実行
vendor/bin/phpunit tests/Unit/Service/DailyArticleResultService/DailyArticleResultServiceTest.php
# または、Composerスクリプト経由で実行
vendor/bin/phpunit -- tests/Unit/Service/DailyArticleResultService/DailyArticleResultServiceTest.php特定のテストメソッドを実行
特定のテストメソッドのみを実行する場合:
bash
# --filter オプションを使用
vendor/bin/phpunit --filter test_method_name
# 例: DailyArticleResultServiceTestの特定のメソッドを実行
vendor/bin/phpunit --filter DailyArticleResultServiceTest::test_specific_methodフィルタを使った実行
テストクラス名でフィルタ
bash
vendor/bin/phpunit --filter DailyArticleResultServiceTestテストスイートでフィルタ
bash
# Unitテストのみ実行
vendor/bin/phpunit tests/Unit
# パフォーマンステストのみ実行
vendor/bin/phpunit tests/Performanceテスト構成の説明
phpunit.xml の設定内容
プロジェクトルートにある phpunit.xml ファイルで、PHPUnitの動作を設定しています。
主要な設定項目
- bootstrap:
tests/bootstrap.php- テスト実行前に読み込まれるファイル - colors:
true- カラー出力を有効化 - processIsolation:
false- テストを個別のプロセスで実行しない(高速化) - stopOnFailure:
false- エラーが発生しても全テストを実行 - cacheDirectory:
.phpunit.cache- キャッシュディレクトリ - executionOrder:
depends,defects- 依存関係と欠陥のあるテストを優先実行
テストスイート
xml
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
</testsuites>現在は Unit テストスイートのみが定義されています。パフォーマンステストは手動で指定して実行します。
ソースコードの指定
xml
<source>
<include>
<directory>core_src</directory>
</include>
</source>カバレッジレポート生成時に、core_src/ ディレクトリ内のコードを対象とします。
拡張(遅いテストの一覧)
xml
<extensions>
<bootstrap class="Ergebnis\PHPUnit\SlowTestDetector\Extension">
<parameter name="maximum-count" value="15"/>
<parameter name="maximum-duration" value="1000"/>
</bootstrap>
</extensions>ergebnis/phpunit-slow-test-detector が、1 秒(ミリ秒で指定)を超えたテストを最大 15 件、実行結果の末尾に表示します。表示だけで失敗にはなりません。見方は「テストが遅い」を参照してください。
PHP設定
xml
<php>
<ini name="memory_limit" value="-1"/> <!-- メモリ制限なし -->
<ini name="error_reporting" value="-1"/> <!-- すべてのエラーを報告 -->
<ini name="display_errors" value="1"/> <!-- エラーを表示 -->
</php>テストディレクトリ構造
tests/
├── bootstrap.php # テスト実行前の初期化ファイル
├── Unit/ # ユニットテスト
│ ├── Factory/
│ ├── Model/
│ ├── PostType/
│ ├── Repository/
│ ├── Service/
│ ├── ShortCode/
│ └── Util/
└── Performance/ # パフォーマンステスト
└── ConverterPerformanceTest.phpbootstrap.php の役割
tests/bootstrap.php は、すべてのテストの実行前に読み込まれるファイルです。以下の処理を行います:
- Brain Monkeyの有効化: WordPress関数のモックを有効化
- Composerオートローダーの読み込み: アプリケーションクラスとテストツールを読み込み
- WordPress関数のモック:
wpdb、wp_cache_get、wp_cache_setなどのモック定義 - 環境変数の設定: テーマディレクトリパスなどの設定
テスト結果の見方
成功時の出力
テストが成功した場合、以下のような出力が表示されます:
PHPUnit 11.x.x by Sebastian Bergmann and contributors.
Runtime: PHP 8.3.x
Configuration: /path/to/phpunit.xml
................. 17 / 17 (100%)
Time: 00:00.123, Memory: 12.00 MB
OK (17 tests, 45 assertions)- OK: すべてのテストが成功
- 17 tests: 実行されたテストメソッドの数
- 45 assertions: 実行されたアサーション(検証)の数
失敗時の出力
テストが失敗した場合、以下のような出力が表示されます:
PHPUnit 11.x.x by Sebastian Bergmann and contributors.
Runtime: PHP 8.3.x
Configuration: /path/to/phpunit.xml
F 1 / 1 (100%)
Time: 00:00.045, Memory: 8.00 MB
There was 1 failure:
1) Tests\Unit\Service\DailyArticleResultServiceTest::test_something
Failed asserting that false is true.
/path/to/tests/Unit/Service/DailyArticleResultServiceTest.php:42
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.- F: 失敗したテスト(Failure)
- Failed asserting that false is true: アサーションの失敗内容
- /path/to/...: 失敗が発生したファイルと行番号
エラーメッセージの読み方
よくあるエラーメッセージ
Failed asserting that...
- アサーション(検証)が失敗
- 期待値と実際の値が異なる
Class 'App\SomeClass' not found
- クラスが見つからない
- オートローダーの問題、または名前空間の誤り
Call to undefined function...
- 関数が定義されていない
- WordPress関数のモックが正しく設定されていない可能性
Undefined property...
- プロパティが定義されていない
- モックオブジェクトの設定が不完全
カバレッジレポートの確認方法
HTMLレポートの確認
bash
composer test-coverage実行後、reports/coverage/index.html をブラウザで開きます。
カバレッジレポートの見方
- 緑色: テストでカバーされている行
- 赤色: テストでカバーされていない行
- 黄色: 部分的にカバーされている行(条件分岐など)
カバレッジレポートでは、以下の情報を確認できます:
- ファイルごとのカバレッジ率
- 行ごとの実行回数
- メソッドごとのカバレッジ率
実際のテストファイル例
ユニットテストの例
プロジェクトには以下のようなユニットテストが含まれています:
- Service層のテスト:
tests/Unit/Service/DailyArticleResultService/DailyArticleResultServiceTest.php - Repository層のテスト:
tests/Unit/Repository/DailyDataRepositoryBehaviorTest.php - Factory層のテスト: (ConverterFactory は削除済み。Factory テストは該当クラスがある場合に追加)
- Util層のテスト:
tests/Unit/Util/HeatMapClassGeneratorTest.php
パフォーマンステストの例
- Converterのパフォーマンステスト:
tests/Performance/ConverterPerformanceTest.php- シングルトン化によるパフォーマンス向上を検証
- 大量のインスタンス作成の実行時間を測定
テストファイルの構造
典型的なテストファイルの構造:
php
<?php
namespace Tests\Unit\Service;
use PHPUnit\Framework\TestCase;
use App\Service\SomeService;
class SomeServiceTest extends TestCase {
private SomeService $service;
protected function setUp(): void {
// テスト実行前の初期化処理
$this->service = new SomeService();
}
public function test_something(): void {
// テストの実装
$result = $this->service->doSomething();
$this->assertTrue($result);
}
}トラブルシューティング
よくあるエラーと解決方法
1. "Class not found" エラー
症状: Class 'App\SomeClass' not found というエラーが発生
原因: オートローダーが正しく読み込まれていない
解決方法:
bash
# ルートおよび core_src で composer install を実行
composer install
cd core_src && composer install2. WordPress関数のエラー
症状: Call to undefined function wp_cache_get() などのエラー
原因: WordPress関数のモックが正しく設定されていない
解決方法: tests/bootstrap.php が正しく読み込まれているか確認。phpunit.xml の bootstrap 設定を確認。
3. メモリ不足エラー
症状: Fatal error: Allowed memory size exhausted
解決方法: phpunit.xml でメモリ制限が -1(無制限)に設定されているか確認。それでも問題が発生する場合は、PHPの設定を確認:
bash
php -d memory_limit=-1 vendor/bin/phpunit4. テストが遅い
原因: テストが多すぎる、または個別のテストが重い
解決方法:
- 特定のテストファイルのみ実行
processIsolationがfalseに設定されているか確認(phpunit.xml)composer testの末尾に出る遅いテストの一覧を見る。phpunit.xmlに登録したergebnis/phpunit-slow-test-detectorが、1 秒(maximum-duration)を超えたテストを遅い順に最大 15 件(maximum-count)表示する- 一覧は表示だけで、テストは失敗にならない。上位に同じクラスが並ぶときは、大きなファイルの生成や
RunInSeparateProcessなど、そのテストだけが重い理由がないかを確認する
5. カバレッジレポートが生成されない
原因: XdebugまたはPCOVがインストールされていない
解決方法: PHP拡張機能をインストール:
bash
# Xdebugをインストール(例: macOS + Homebrew)
pecl install xdebug
# または、PCOVをインストール
pecl install pcovインストール後、php.ini に追加:
ini
; Xdebugの場合
zend_extension=xdebug.so
; PCOVの場合
extension=pcov.so次のステップ
関連ドキュメント
- 品質管理ツール全体の概要
- PHPStan実行ガイド - 静的解析
- PHPCS実行ガイド - コーディング規約チェック
テストを書く
新しい機能を実装する際は、必ずテストも作成してください:
- テストファイルを
tests/Unit/の適切なディレクトリに作成 - テストクラスは
Tests\Unit\名前空間を使用 PHPUnit\Framework\TestCaseを継承- テストメソッド名は
test_で始める
ベストプラクティス
- 1つのテストメソッドで1つのことをテスト: テストが失敗したときに原因を特定しやすくする
- 明確なアサーション: 何を検証しているかが分かるようにする
- モックの適切な使用: 外部依存をモックして、テストを独立させる
- テストデータの準備:
setUp()メソッドで共通の初期化処理を行う
書かないテスト
テストの件数やカバレッジを増やすこと自体は目的にしません。次のテストは、壊れたコードを検出できないか、他の仕組みで既に保証されているため書きません(Issue #4007)。
- クラス・メソッドの存在確認(
assertTrue(method_exists(...))、class_exists): 存在しなければ呼び出し側のテストや PHPStan が失敗する。存在するだけでは振る舞いを何も保証しない。tests/UnitのassertTrue(method_exists(...))/class_exists/interface_existsはcomposer phpstan-tests(CI Quality Gate の「Run PHPStan (tests)」)でエラーになる - インターフェース実装・コンストラクタの戻り値の型だけを確認するテスト(
test_implements_interface、test_can_be_instantiated): 型は PHPStan / Deptrac / TypeScript が保証する - 定数の値を書き写して比べるだけのテスト(
assertSame('foo', Foo::BAR)): 定数を変えるときはテストも同じように書き換えるだけなので、誤りを検出できない。assertSame/assertEqualsの一方がリテラル、もう一方がFoo::BAR形式のクラス定数(self::/static::/parent::と::classは除く)のものはcomposer phpstan-testsでエラーになる(Issue #4013)。リテラルとみなすのは、文字列・数値・真偽値と、キーがそれらのリテラル、値がそれらのリテラル・null・入れ子の配列リテラルの配列リテラル([ 'a', 'b' ]、[ 'key' => [ 1, 2 ] ]、[ 'a' => null ]、[]。Issue #4022)。nullとの比較(配列の要素のnullは検出対象)、enum の->value、グローバル定数、文字列連結、変数・定数・関数呼び出し・展開を含む配列との比較はルールの対象外なので、レビューで見る例外: 外部との契約になっている値(DB のカラム名・REST のルート・post meta キーなど、変えると本番データや外部の呼び出し元が壊れる値)は書いてよい。その場合は、なぜ値を固定するのかをアサーションの直前の行に日本語のコメントで残す(行コメント・ブロックコメント・docblock のいずれも可。複数行に分けてもよい)。ルールが理由とみなすのは、日本語(ひらがな・カタカナ・漢字)を含み、記号・空白を除いて 10 文字以上のコメント。
// Assertや// 検証のような見出し・短いメモ、空行を挟んだコメント、行末コメント(前の文の$x = 1; // 補足や、ブロックを開く行のif ( ... ) { // 補足)、phpcs:/@phpstan-などツール向けの指示の行(phpcs:ignore ... -- 補足の補足も含む)は理由とみなさない。理由コメントのあるアサーションに空行や別の文を挟まず続く同種のアサーションは、先頭のコメントでまとめて許可される。コメントの中身が理由として妥当かは人のレビューで見るphp// 本番 DB の既存固定ページのスラッグ(公開 URL)と一致させる必要がある $this->assertSame( 'tokushoho', LegalPagesConstants::TOKUSHOHO_PAGE_SLUG ); $this->assertSame( 'faq', LegalPagesConstants::FAQ_PAGE_SLUG );
- モックの戻り値をそのまま assert するだけのテスト: テスト対象のロジックを通らないため、モックの設定を確認しているだけになる
- 既存テストと同じ経路を別ファイル・別名で重ねるテスト: 失敗したときの修正箇所が増えるだけで、検出できる不具合は増えない。追加する前に、同じクラスの既存の
*Test.php/*BehaviorTest.phpを確認する - カバレッジを埋めるためだけの getter / setter のテスト: 値を入れて取り出すだけでは分岐も変換もないため、回帰を防がない
書くテスト
- 振る舞いを検証するテスト: 分岐・変換・バリデーション・エラー処理など、入力によって結果が変わる部分。コードを壊すと失敗するテストだけが回帰を防ぐ
- セキュリティのテスト: 権限・nonce・サニタイズ・リダイレクト先の検証。抜けると本番で被害が出るため、正常系だけでなく拒否される経路も書く
- バグ修正時の回帰テスト: 修正前のコードで失敗することを確かめてから追加する。修正前でも通るテストは、そのバグを防いでいない
- 似た入力のパターンは DataProvider にまとめる: メソッドを増やさず、入力と期待値の組を並べる。パターンの追加と見比べがしやすくなる
その他
RunInSeparateProcessは、定数の定義やrequireなどプロセスの状態を変えるテストだけに使い、理由をコメントで残す: 別プロセスはテストを大きく遅くするため、必要な理由がテストから読めるようにする。#[RunInSeparateProcess](メソッド)/#[RunTestsInSeparateProcesses](クラス)の docblock か属性の直前(属性の間も可)のコメントに「別プロセス」「別のプロセス」「プロセス分離」「プロセスを分離」「プロセスを分ける(分けて)」のいずれかを含む文がないものはcomposer phpstan-testsでエラーになる(Issue #4013)。理由は日本語で書く(英語のコメントは理由とみなさない)。テストの説明だけの docblock は理由とみなさない。定数の書き写しの理由コメントと同じく、空行を挟んだコメント(区切りコメントなど)、行末コメント、phpcs:/@phpstan-などツール向けの指示の行に書いた文は理由とみなさない(Issue #4022)。クラスに理由を書いても、メソッドの#[RunInSeparateProcess]にはメソッド側で理由が要る。「以下 N 件も同じ」のようにまとめず、属性ごとに理由を書くphp/** * WP_DEBUG を define するため別プロセスで実行する(同一プロセスだと後続テストの WP_DEBUG 分岐が変わる)。 */ #[RunInSeparateProcess] public function test_xxx(): void {テストを追加したこと自体を成果にしない: PR 説明には、追加したテストがどの回帰(どの変更で壊れたら失敗するか)を防ぐかを書く
Cursor では tests/**/*.php と **/*.test.{ts,tsx,js,mjs}(Vitest のテストと、scripts/ や docs/.vitepress/scripts/ にある node --test 用の *.test.mjs)を編集するときに .cursor/rules/testing-guidelines.mdc が適用されます。Vitest 側の補足は Vitest 実行ガイド を参照してください。
最終更新: 2026-10-07