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 のコードは、純粋な処理・関数の差し替え・統合テストに分けて考える

お気軽にご相談ください

お見積りへ お問い合わせへ
▲