mcpField
P
phpunit

phpunit

github.com/xobotyiテスト・品質23338 回インストール今日更新
リポジトリ

phpunit について

PHP開発者向けのPHPUnitテストスキルで、PHPUnit 11以降とPHP 8.5以降を対象としています。振る舞いを重視し、フィクスチャ、データプロバイダー、アサーション、モック、属性、テスト構成を扱います。

主な機能

  1. 01振る舞いを表すテスト名
  2. 02Arrange-Act-Assert構成
  3. 03フィクスチャのライフサイクル指針
  4. 04データプロバイダーのパターン
  5. 05アサーションと例外のテスト
  6. 06スタブ、モック、PHP属性

活用シーン

  1. 01PHPUnitテストクラスの作成
  2. 02例外とデータ駆動の振る舞いのテスト
  3. 03依存関係に使うスタブやモックの選択

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/xobotyi/cc-foundry --skill phpunit

インストール後、Claude Code で/phpunitを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

.dev/
reference-inventory.json
references/
assertions.md
configuration.md
data-providers.md
mocking.md

詳細

PHPUnit

実装ではなく、振る舞いをテストしましょう。テストは実行可能なドキュメントです。テスト名を読んでもコードの動作がわからない場合は、書き直してください。

PHPUnit は PHP 標準のテストフレームワークです。TestCase を継承するテストケースクラス、フィクスチャ用の setUp()/tearDown()、そして豊富なアサーション API を使用します。すべてのパターンは、PHP 8.5 以降での PHPUnit 11 以降を対象としています。PHP 8 の属性のみを使用してください。アノテーションは 11 で非推奨となり、12 で削除されます。

参考資料

  • アサーションカタログ、制約、例外の検証 → [${CLAUDE_SKILL_DIR}/references/assertions.md] — カテゴリ別にまとめたアサーション API 全般、制約システム、カスタムアサーション
  • テストダブル — スタブ、モック、MockBuilder → [${CLAUDE_SKILL_DIR}/references/mocking.md] — createStub と createMock の使い分け、戻り値の設定、呼び出しマッチャー、引数の制約、MockBuilder
  • データプロバイダー — 静的、名前付き、ジェネレーター → [${CLAUDE_SKILL_DIR}/references/data-providers.md] — #[DataProvider]、#[TestWith]、名前付きデータセット、ジェネレータープロバイダー、外部プロバイダー
  • phpunit.xml の構造、テストスイート、ソース設定 → [${CLAUDE_SKILL_DIR}/references/configuration.md] — XML 要素、厳格な設定、source 要素、カバレッジレポート、実行順序

テストの構造

テストの検出と命名
  • ファイル: 設定済みのテストディレクトリに置く *Test.php。ソースの構造を反映させます。例:src/Service/PaymentService.php → tests/Unit/Service/PaymentServiceTest.php。
  • クラス: final class PaymentServiceTest extends TestCase。必ず final にします。
  • メソッド: test プレフィックスまたは #[Test] 属性を使用します。動作を表す名前にします。testSearch ではなく、testReturnsEmptyCollectionWhenNoResults のようにします。
  • 本番クラスごとにテストクラスを 1 つ作成します。 Unit/Integration ディレクトリに分けます。
Arrange-Act-Assert

すべてのテストを次の 3 つの段階で構成します。

public function testUserCreationSetsDefaults(): void
{
    // Arrange
    $data = ['name' => 'Alice', 'email' => '[email protected]'];

    // Act
    $user = User::fromArray($data);

    // Assert
    $this->assertSame('Alice', $user->getName());
    $this->assertTrue($user->isActive());
    $this->assertSame([], $user->getRoles());
}
  • 1 つのテストにつき、実行する操作は 1 つです。 複数の操作が必要なら、複数のテストを書きます。
  • 段階が明らかな場合、コメントは任意です。 段階がすぐにはわからないほどテストが長い場合は、コメントを追加します。
テストの粒度
  • 1 つのテストにつき、概念は 1 つです。 同じ振る舞いを検証する複数のアサーションは問題ありません。振る舞いが独立している場合は、テストを分けます。
  • 通常は高速に実行できるようにします。 ユニットテストはミリ秒単位で実行してください。遅いテストは #[Group('slow')] グループで制御します。
  • テストの分離は必須です。 テストは実行順序や共有された変更可能な状態に依存してはなりません。各テストで独自の環境を用意します。

フィクスチャ

setUp() / tearDown()
  • setUp() は、テストメソッドごとに新しいインスタンスで実行されます。ここで SUT とそのスタブを作成します。
  • tearDown() は各テストの後に実行されます。外部リソース(ファイル、ソケット、データベース接続)を扱う場合にのみ必要です。通常のオブジェクトのクリーンアップには不要です。
  • setUpBeforeClass() / tearDownAfterClass() はクラスごとに 1 回実行されます。データベース接続など、コストの高い共有リソースに使用します。static プロパティに保存します。
final class PaymentServiceTest extends TestCase
{
    private PaymentService $service;
    private Gateway&Stub $gateway;

    protected function setUp(): void
    {
        $this->gateway = $this->createStub(Gateway::class);
        $this->service = new PaymentService($this->gateway);
    }
}
フィクスチャのライフサイクル
  • setUpBeforeClass() — クラススコープ。最初のテストの前に 1 回実行

  • setUp() — メソッドスコープ。各テストの前に実行

  • assertPreConditions() — メソッドスコープ。setUp の後、テストの前に実行

  • assertPostConditions() — メソッドスコープ。テストの後、tearDown の前に実行

  • tearDown() — メソッドスコープ。各テストの後に実行

  • tearDownAfterClass() — クラススコープ。最後のテストの後に 1 回実行

  • 抽象テストケースを継承する場合は、parent::setUp() を呼び出します。 そうしないと、親のフィクスチャ設定が黙ってスキップされます。

  • 複数のセットアップメソッドが必要な場合は、#[Before] / #[After] 属性を使用します(壊れやすい parent::setUp() の連鎖を避けられます)。

データプロバイダー

基本的な使い方
use PHPUnit\Framework\Attributes\DataProvider;

#[DataProvider('additionCases')]
public function testAdd(int $a, int $b, int $expected): void
{
    $this->assertSame($expected, $a + $b);
}

public static function additionCases(): array
{
    return [
        'zeros'        => [0, 0, 0],
        'positive sum' => [1, 2, 3],
        'negative'     => [-1, 1, 0],
    ];
}
  • プロバイダーは public static にする必要があります。 非 static のプロバイダーは PHPUnit 11 で削除されました。
  • 常に名前付きデータセットを使用します。 文字列キーを使うと、失敗時の出力が読みやすくなります。
  • @dataProvider アノテーションではなく、#[DataProvider] 属性を使用します。
インラインデータ

少量で単純なデータセットには、プロバイダーメソッドは必要ありません。

use PHPUnit\Framework\Attributes\TestWith;

#[TestWith([0, 0, 0])]
#[TestWith([1, 2, 3])]
#[TestWith([-1, 1, 0])]
public function testAdd(int $a, int $b, int $expected): void
{
    $this->assertSame($expected, $a + $b);
}
ジェネレータープロバイダー

大量のデータセットや、計算によって生成するデータセットに使用します。

public static function boundaryCases(): Generator
{
    yield 'min int' => [PHP_INT_MIN, 0, PHP_INT_MIN];
    yield 'max int' => [PHP_INT_MAX, 0, PHP_INT_MAX];
}
プロバイダールール
  • データはスカラーまたは不変である必要があります — プロバイダーにサービスオブジェクトや複雑なグラフを含めないでください。
  • プロバイダーでモックオブジェクトを使用しないでください — プロバイダーの実行中はフレームワークが初期化されていません。
  • 空のプロバイダーは禁止です — PHPUnit 11 では InvalidDataProviderException がスローされます。
  • 複数のプロバイダーを 1 つのテストメソッドに重ねて指定できます — データセットが組み合わされます。

外部プロバイダー、TestDox との連携、エッジケースについては、${CLAUDE_SKILL_DIR}/references/data-providers.md を参照してください。

アサーション

基本的なアサーション
$this->assertSame($expected, $actual);        // Strict === (preferred)
$this->assertEquals($expected, $actual);       // Loose == (use sparingly)
$this->assertTrue($condition);
$this->assertFalse($condition);
$this->assertNull($value);
$this->assertInstanceOf(Expected::class, $obj);
$this->assertCount(3, $collection);
$this->assertEmpty($collection);
$this->assertArrayHasKey('key', $array);
$this->assertContains($needle, $haystack);     // Strict comparison
  • assertEquals() より assertSame() を優先してください — 厳密な型比較により、より多くのバグを検出できます。
  • 1 つのテストで複数のアサーションを使用してもかまいません — 同じ振る舞いを検証する場合に限ります。
文字列のアサーション
$this->assertStringStartsWith('Error:', $message);
$this->assertStringEndsWith('.php', $filename);
$this->assertStringContainsString('needle', $haystack);
$this->assertMatchesRegularExpression('/^\d{4}-\d{2}$/', $date);
浮動小数点数の比較
$this->assertEqualsWithDelta(3.14, $result, 0.01);
例外のテスト
public function testThrowsOnInvalidInput(): void
{
    $this->expectException(InvalidArgumentException::class);
    $this->expectExceptionMessage('must be positive');

    $calculator->divide(1, 0);
}
  • 例外をスローするコードより前に expectException() を呼び出してください — 期待値を設定します。
  • 例外の型が広範な場合は expectExceptionMessage() を使用してください — メッセージに部分文字列が含まれていることを検証します。
  • 正規表現で照合する場合は expectExceptionMessageMatches() を使用してください。
非推奨/エラーの期待値
public function testTriggersDeprecation(): void
{
    $this->expectUserDeprecationMessage('use newMethod() instead');

    $service->oldMethod();
}

アサーションの全カタログ、制約システム、書式文字列のアサーションについては、${CLAUDE_SKILL_DIR}/references/assertions.md を参照してください。

モック

スタブとモック
  • スタブ(createStub())— 戻り値を制御します。呼び出しの検証は行いません。
  • モック(createMock())— メソッドが呼び出されたか、引数が一致したかなど、やり取りを検証します。

通常はスタブを使用してください。副作用が発生したことを検証する場合にのみ、モックを使用してください。

スタブの作成
$repo = $this->createStub(UserRepository::class);
$repo->method('find')->willReturn(new User(name: 'Alice'));

$service = new UserService($repo);
$result = $service->getUser(1);

$this->assertSame('Alice', $result->name);

複数のメソッドを簡潔に指定する方法:

$repo = $this->createConfiguredStub(UserRepository::class, [
    'find'   => new User(name: 'Alice'),
    'exists' => true,
]);
モックの作成
$logger = $this->createMock(Logger::class);
$logger->expects($this->once())
    ->method('error')
    ->with($this->stringContains('payment failed'));

$service = new PaymentService($logger);
$service->process($invalidPayment);
戻り値の設定
$stub->method('fetch')->willReturn('value');              // Fixed value
$stub->method('fetch')->willReturn('a', 'b', 'c');       // Consecutive values
$stub->method('fetch')->willReturnArgument(0);            // Return first arg
$stub->method('fetch')->willReturnSelf();                 // Fluent interface
$stub->method('fetch')->willReturnCallback(fn ($id) => "item-{$id}");
$stub->method('fetch')->willThrowException(new RuntimeException('fail'));
$stub->method('fetch')->willReturnMap([
    ['key1', 'value1'],
    ['key2', 'value2'],
]);
モックのルール
  • 境界でモックを使用してください。 外部サービス、データベース、ファイルシステム、時計などをモックし、内部関数はモックしないでください。
  • フェイクやインメモリ実装が利用できる場合は、自分たちが所有するものをモックしないでください。
  • 複雑なモック設定より依存性注入を優先してください。 協力オブジェクトをコンストラクターのパラメーターとして渡し、テストでスタブを使用してください。
  • テスト対象そのものをモックしないでください。 SUT の一部をモックする必要がある場合、SUT の責務が多すぎます — 分割してください。
  • テストダブルにはクラスよりインターフェースを優先してください — 制約が少なく、よりよい設計につながります。
  • スタブに対して expects() を呼び出さないでください — バージョン 11 では非推奨で、バージョン 12 ではエラーになります。

MockBuilder、交差型、呼び出しマッチャー、PHP 8.4 のプロパティフックについては、${CLAUDE_SKILL_DIR}/references/mocking.md を参照してください。

属性

PHPUnit 11 では PHP 8 の属性のみを使用します。すべての属性は PHPUnit\Framework\Attributes 名前空間にあります。

テストのメタデータ
  • #[Test] — test* 以外のメソッドをテストとしてマークします
  • #[DataProvider('method')] — データプロバイダーを紐付けます
  • #[DataProviderExternal(Class::class, 'method')] — 外部データプロバイダーを指定します
  • #[TestWith([args])] — インラインデータプロバイダーを指定します
  • #[TestDox('description')] — TestDox のカスタム説明を指定します
  • #[Depends('testMethod')] — テストの依存関係を宣言します
  • #[Group('name')] — グループを割り当てます
  • #[Ticket('PROJ-123')] — イシュートラッカーへのリンクを設定します
スキップ/条件指定
  • #[RequiresPhp('>= 8.4')] — PHP のバージョンが一致しない場合にスキップします
  • #[RequiresPhpExtension('pdo_pgsql')] — 拡張機能がない場合にスキップします
  • #[RequiresOperatingSystemFamily('Linux')] — その他の OS でスキップします
  • #[RequiresFunction('sodium_crypto_sign')] — 関数がない場合にスキップします
  • #[RequiresMethod(PDO::class, 'sqliteCreateFunction')] — メソッドがない場合にスキップします
カバレッジ
  • #[CoversClass(ClassName::class)] — このクラスがテスト対象であることを示します
  • #[CoversFunction('functionName')] — この関数がテスト対象であることを示します
  • #[CoversMethod(ClassName::class, 'method')] — このメソッドがテスト対象であることを示します
  • #[CoversNothing] — テストがカバレッジに寄与しないことを示します(統合テスト)
  • #[UsesClass(ClassName::class)] — 使用は許可されますが、カバレッジ対象にはならない依存クラスを指定します
  • #[UsesFunction('functionName')] — 使用は許可されますが、カバレッジ対象にはならない関数を指定します
Fixture
  • #[Before] — 各テストの前にメソッドを実行します(setUp の代替)。
  • #[After] — 各テストの後にメソッドを実行します(tearDown の代替)。
  • #[BeforeClass] — 最初のテストの前に静的メソッドを実行します。
  • #[AfterClass] — 最後のテストの後に静的メソッドを実行します。
  • #[BackupGlobals(true)] — このテストのグローバル変数をバックアップ/復元します。
  • #[BackupStaticProperties(true)] — 静的プロパティをバックアップ/復元します。
テストの動作
  • #[DoesNotPerformAssertions] — リスキーなテストに関する警告を抑制します。
  • #[RunInSeparateProcess] — 別の PHP プロセスで分離して実行します。
  • #[RunTestsInSeparateProcesses] — クラス内のすべてのテストを分離して実行します。
  • #[Small] / #[Medium] / #[Large] — 時間制限を適用します(1 秒/10 秒/60 秒)。

テストの構成

ディレクトリ構造
tests/
├── Unit/                     # Fast, isolated, no I/O
│   ├── Service/
│   │   └── PaymentServiceTest.php
│   └── Model/
│       └── UserTest.php
├── Integration/              # Real dependencies, slower
│   └── Repository/
│       └── UserRepositoryTest.php
└── bootstrap.php             # Autoloader for tests
  • tests/Unit/ と tests/Integration/ の下に、ソースディレクトリ構造を反映させます。
  • ユニットテスト — データベース、ファイルシステム、ネットワークは使用しません。すべての境界をモックします。
  • 統合テスト — 実際の依存関係を使用します。カバレッジ指標への影響を避けるため、#[CoversNothing] を付けます。
テストスイート

選択的に実行できるよう、phpunit.xml で定義します。

<testsuites>
    <testsuite name="unit">
        <directory>tests/Unit</directory>
    </testsuite>
    <testsuite name="integration">
        <directory>tests/Integration</directory>
    </testsuite>
</testsuites>

一部を実行するには、phpunit --testsuite unit、phpunit --group slow を使用します。

設定

推奨する phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.5/phpunit.xsd"
         bootstrap="vendor/autoload.php"
         colors="true"
         cacheDirectory=".phpunit.cache"
         executionOrder="depends,random"
         beStrictAboutTestsThatDoNotTestAnything="true"
         beStrictAboutOutputDuringTests="true"
         failOnWarning="true"
         failOnRisky="true"
         failOnDeprecation="true"
         failOnNotice="true">
    <testsuites>
        <testsuite name="unit">
            <directory>tests/Unit</directory>
        </testsuite>
        <testsuite name="integration">
            <directory>tests/Integration</directory>
        </testsuite>
    </testsuites>
    <source restrictDeprecations="true"
            restrictNotices="true"
            restrictWarnings="true">
        <include>
            <directory suffix=".php">src</directory>
        </include>
    </source>
</phpunit>
主な設定の選択
  • executionOrder="depends,random" — 明示的な #[Depends] を尊重しながらテストの順序をランダム化し、隠れた依存関係を検出します。
  • beStrictAboutTestsThatDoNotTestAnything="true" — アサーションのないテストをリスキーとして扱います。
  • failOnDeprecation="true" — コード内の非推奨機能を早期に検出します。
  • restrictDeprecations を指定した <source> — ベンダー依存関係ではなく、自分のコードに関する問題のみを表示します。
  • cacheDirectory — .gitignore に .phpunit.cache を追加します。
コードカバレッジ

PCOV または Xdebug 拡張機能が必要です。

phpunit --coverage-html build/coverage --coverage-clover build/clover.xml

#[CoversClass] 属性と #[UsesClass] 属性を使って、カバレッジの対象を正確に指定します。beStrictAboutCoverageMetadata="true" を設定すると、カバレッジ属性のないテストはリスキーとして扱われます。

完全な XML リファレンス、カバレッジレポートの種類、実行順序のオプションについては、${CLAUDE_SKILL_DIR}/references/configuration.md を参照してください。

適用

テストを作成するときは、各ルールに従っていることを逐一説明せず、すべての規約を黙って適用します。プロジェクト既存のテストスタイルに合わせてください。既存のコードベースが規約と矛盾する場合は、コードベースに従い、相違点を一度だけ指摘します。

テストをレビューするときは、具体的な問題を示し、修正をその場で提示します。説教せず、何が問題で、どう直すべきかを述べます。

Bad:  "According to PHPUnit best practices, you should use createStub
       instead of createMock when you don't need expectations..."
Good: "createMock → createStub (no expects() call, stub is sufficient)"

統合

言語の選択は php スキルが定め、このスキルは PHPUnit のテストに関する判断を定めます。ワークフロー(調査、計画、検証)は coding スキルが定めます。

実装ではなく、動作をテストします。迷ったら、モックを減らしてください。

コメント