2026年8月21日
2026年8月21日
PHPUnitの書き方入門:PHPのユニットテストを導入・作成・実行する手順
PHP のユニットテストは、PHPUnit を Composer で開発用に入れ、tests ディレクトリに TestCase を継承したクラスを書き、vendor/bin/phpunit で実行する、という流れで始められます。この記事では、テストしやすいコードの形、アサーションの選び方、データプロバイダー、テストダブル(スタブとモック)、カバレッジの取り方、CI での実行までを、1 つの小さなクラスを例に説明します。WordPress のプラグインやテーマのコードをテストするときの考え方にも触れます。
PHPUnitの版とPHPのバージョン
PHPUnit はおおむね毎年 2 月ごろに新しいメジャー版が出て、そのたびに対応する PHP の最低バージョンが上がります。たとえば PHPUnit 11 は PHP 8.2 以上、PHPUnit 12 は PHP 8.3 以上が必要です(2026年10月現在)。版の対応表は PHPUnit 公式サイトのサポート情報で確認できます。
プロジェクトを動かす PHP のバージョンに合った版を入れることが大切です。次のように版を指定せずに追加すると、Composer が現在の PHP で動く版を選びます。
composer require --dev phpunit/phpunit
--dev を付けると require-dev に入り、本番に配置するときに composer install --no-dev で除外できます。テスト用のツールを本番に置かないためにも、この区別は守ってください。
ここから先の書き方は PHPUnit 10 以降を前提にしています。古い記事にある @test や @dataProvider などのドキュメントコメントによる指定は、PHPUnit 11 で非推奨、PHPUnit 12 で廃止され、PHP 8 のアトリビュート(#[Test] など)に置き換わっています。
ディレクトリ構成とオートロード
テスト対象のコードを src、テストを tests に置く構成が一般的です。
my-project/
├── composer.json
├── phpunit.xml
├── src/
│ ├── PriceCalculator.php
│ └── TaxRateProvider.php
└── tests/
└── PriceCalculatorTest.php
composer.json にオートロードの設定を書くと、require を並べずにクラスを読み込めます。
{
"autoload": {
"psr-4": { "App\\": "src/" }
},
"autoload-dev": {
"psr-4": { "Tests\\": "tests/" }
}
}
設定を変えたら composer dump-autoload を実行して反映します。「クラスが見つからない」というエラーの多くは、名前空間とディレクトリの対応のずれか、この再生成のし忘れが原因です。
phpunit.xmlを用意する
設定ファイルは vendor/bin/phpunit --generate-configuration で対話的に作れます。最小限の内容は次のとおりです。
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true">
<testsuites>
<testsuite name="unit">
<directory>tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>src</directory>
</include>
</source>
</phpunit>
bootstrap でオートローダーを読み込み、testsuites でテストの場所を指定します。source はカバレッジなどの対象にするコードの範囲です。PHPUnit 9 以前の coverage 要素の書き方は、新しい版では警告になります。
テストしやすいコードを書く
テストのしやすさは、対象のコードの形でほぼ決まります。例として、税込価格を計算するクラスを用意します。税率を外から受け取るようにしておくと、テストでは好きな税率に差し替えられます。
<?php
// src/TaxRateProvider.php
namespace App;
interface TaxRateProvider {
/** 税率をパーセントの整数で返す(例: 10) */
public function percentFor( string $category ): int;
}
<?php
// src/PriceCalculator.php
namespace App;
final class PriceCalculator {
public function __construct( private TaxRateProvider $rates ) {}
public function withTax( int $price, string $category ): int {
if ( $price < 0 ) {
throw new \InvalidArgumentException( 'price must not be negative' );
}
$percent = $this->rates->percentFor( $category );
// 小数の誤差を避けるため整数で計算し、1円未満は切り捨てる
return intdiv( $price * ( 100 + $percent ), 100 );
}
}
関数の中でデータベースや外部 API、現在時刻に直接アクセスすると、テストのたびに結果が変わったり、準備に手間がかかったりします。こうした依存はコンストラクターで受け取る形にしておくと、テストで差し替えられます。
最初のテストを書く
テストクラスは TestCase を継承し、クラス名とファイル名の末尾を Test にします。メソッド名を test で始めるか、#[Test] アトリビュートを付けるとテストとして実行されます。
<?php
// tests/PriceCalculatorTest.php
namespace Tests;
use App\PriceCalculator;
use App\TaxRateProvider;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
final class PriceCalculatorTest extends TestCase {
private PriceCalculator $calculator;
protected function setUp(): void {
// 値を返すだけの差し替え(スタブ)
$rates = $this->createStub( TaxRateProvider::class );
$rates->method( 'percentFor' )->willReturn( 10 );
$this->calculator = new PriceCalculator( $rates );
}
public function testAddsTaxToPrice(): void {
$this->assertSame( 1100, $this->calculator->withTax( 1000, 'standard' ) );
}
#[DataProvider( 'priceProvider' )]
public function testRoundsDownFractions( int $price, int $expected ): void {
$this->assertSame( $expected, $this->calculator->withTax( $price, 'standard' ) );
}
public static function priceProvider(): array {
return [
'端数なし' => [ 100, 110 ],
'端数を切り捨て' => [ 999, 1098 ],
'1円' => [ 1, 1 ],
'0円' => [ 0, 0 ],
];
}
public function testRejectsNegativePrice(): void {
$this->expectException( \InvalidArgumentException::class );
$this->calculator->withTax( -1, 'standard' );
}
}
ポイントは次のとおりです。
setUp()はテストメソッドごとに実行されるため、テスト同士で状態が混ざらない- 比較には
assertEquals()よりassertSame()を優先する。assertEquals()は型を変換して比べるため、'1100'と1100を同じとみなしてしまう - データプロバイダーは
public staticにする(PHPUnit 10 以降)。キーに名前を付けると、失敗したときにどのケースか分かりやすい - 例外を確かめるときは、例外が起きる処理より前に
expectException()を書く
1 つのテストでは 1 つの振る舞いだけを確かめ、テスト名に「何が起きるべきか」を書いておくと、失敗したときに原因を追いやすくなります。
モックで呼び出しを確かめる
スタブが「決まった値を返すだけ」なのに対し、モックは「どのように呼ばれたか」も確かめます。たとえば、正しいカテゴリーで税率を問い合わせているかを確認したい場合です。
public function testAsksRateForGivenCategory(): void {
$rates = $this->createMock( TaxRateProvider::class );
$rates->expects( $this->once() )
->method( 'percentFor' )
->with( 'reduced' )
->willReturn( 8 );
$calculator = new PriceCalculator( $rates );
$this->assertSame( 1080, $calculator->withTax( 1000, 'reduced' ) );
}
呼び出し方を確かめる必要がないなら createStub() を使います。何でもモックで書くと、実装の細部を変えただけでテストが壊れやすくなります。新しい版の PHPUnit では、期待値を設定していないモックに注意が出る場合もあります。
テストを実行する
vendor/bin/phpunit # すべて実行
vendor/bin/phpunit --filter RoundsDown # 名前で絞り込む
vendor/bin/phpunit --testdox # テスト名を文章のように表示
失敗すると、期待値と実際の値の差が表示されます。まず「どのテストの、どのデータで」失敗したかを見て、対象のコードとテストのどちらが誤っているかを判断します。
カバレッジを取る
どの行がテストで実行されたかを調べるには、Xdebug か PCOV が必要です。Xdebug を使う場合は、カバレッジのモードを有効にして実行します。
XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html coverage
coverage/index.html をブラウザで開くと、実行されなかった行が色分けで表示されます。カバレッジの数値は目安で、高くても条件の組み合わせを確かめていないことはあります。分岐や境界値(0、負の数、上限)を確かめているかを見る材料として使ってください。
CIで自動実行する
テストは手元だけでなく、プッシュのたびに自動で実行すると効果が上がります。GitHub Actions を使う場合の例です。アクションの版は更新されるため、使う時点の最新を確認してください。
name: tests
on: [push, pull_request]
jobs:
phpunit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
coverage: none
- run: composer install --no-interaction --prefer-dist
- run: vendor/bin/phpunit
WordPressのコードをテストするとき
WordPress のプラグインやテーマでは、テストの種類を分けて考えると進めやすくなります。
- 計算や変換などの処理は、WordPress の関数に依存しないクラスに切り出し、この記事の方法でそのままテストする
- WordPress の関数を呼ぶ処理を、WordPress を起動せずに確かめたい場合は、関数を差し替える Brain Monkey などのライブラリを使う
- データベースやフックを含めて確かめる統合テストは、WordPress のテスト用ライブラリ(WP-CLI の
wp scaffold plugin-testsで雛形を作れる)や@wordpress/envの環境を使う
WordPress の統合テスト環境が対応する PHPUnit の版は、最新の PHPUnit より古いことがあり、互換用の Yoast PHPUnit Polyfills を併用するのが一般的です。統合テストを組む前に、使う WordPress のバージョンの案内を確認してください。統合テストはテスト用の別データベースで実行し、本番のデータベースを指定しないよう注意します。
まとめ
- PHPUnit は
composer require --devで入れ、PHP のバージョンに合った版を Composer に選ばせる - PHPUnit 10 以降はアトリビュートで指定し、データプロバイダーは
public staticにする - 依存をコンストラクターで受け取る形にすると、スタブやモックで差し替えてテストできる
- 比較は
assertSame()を優先し、境界値や例外のケースもテストする - カバレッジは Xdebug か PCOV で取り、数値より「確かめていない分岐」を見る材料にする
- WordPress のコードは、純粋な処理・関数の差し替え・統合テストに分けて考える