公開日 2026-03-09

PHPUnit入門(最初のテスト1本)

WSL2とDocker環境でPHPUnitを最小導入し、最初のテスト1本を失敗から成功まで通す。

目次

  1. 前提環境
  2. 1. ゴールと非対象
  3. 2. 新規デモ環境を作成して起動する
  4. 3. PHPUnitを導入して最小設定を作る
  5. 4. 失敗する最初のテストを書く
  6. 5. 実装を修正してテストを成功させる
  7. 6. 任意: composer test で実行を短縮する
  8. 7. 詰まりを手順ごとに再確認する
  9. 8. まとめと次の一歩

WSL2 + Docker 環境で PHPUnit を最小導入し、最初のテスト1本を失敗 -> 修正 -> 成功まで通します。
既存記事への依存は置かず、最小サンプルだけで完走できる形に絞ります。

vendor/bin/phpunit を主線にして、実行結果を読みながら進めます。
composer test は最後に任意ショートカットとして扱います。

前提環境

  • Windows 11
  • WSL2(Ubuntu)
  • VS Code(Remote - WSL)
  • Docker Desktop(WSL連携有効)

以降のコマンドは、特記がない限り WSL 側ターミナルで実行します。
サービス名は app に固定しています。

1. ゴールと非対象

この記事で到達する状態:

  • PHPUnit を phpunit/phpunit:^11 で導入できる
  • 最初のテスト1本を失敗 -> 修正 -> 成功まで再現できる
  • vendor/bin/phpunit でテスト実行を継続できる

この記事で扱わない内容:

  • DB統合テスト
  • モック/スタブの詳細運用
  • カバレッジ収集や閾値管理
  • GitHub Actions での自動実行

2. 新規デモ環境を作成して起動する

WSL側のシェルで作業します。
Windows側から始める場合は wsl でUbuntuに入り、以下を実行してください。

作業ディレクトリを作成して移動します。

# Windows側から始める場合のみ実行
# wsl
mkdir -p ~/projects/phpunit-first-test-demo
cd ~/projects/phpunit-first-test-demo
mkdir -p docker/php src tests
code .

compose.yml を作成します。

services:
  app:
    build:
      context: .
      dockerfile: docker/php/Dockerfile
    working_dir: /workspace
    volumes:
      - ./:/workspace
    command: ["sleep", "infinity"]

docker/php/Dockerfile を作成します。
PHPUnit 11 実行に必要な拡張と Composer を入れます。
php:8.5-cli には dom / xml / xmlwriter は標準で入っているため、ここでは追加が必要な mbstring のみを導入します。

FROM php:8.5-cli

RUN apt-get update \
    && apt-get install -y --no-install-recommends git unzip libonig-dev \
    && docker-php-ext-install mbstring \
    && rm -rf /var/lib/apt/lists/*

COPY --from=composer:2 /usr/bin/composer /usr/bin/composer

WORKDIR /workspace

composer.json を作成します。
この時点で autoload.psr-4src/ を登録しておくと、後続のテストでクラス解決に詰まりにくくなります。

{
  "name": "example/phpunit-first-test-demo",
  "type": "project",
  "require": {},
  "require-dev": {},
  "autoload": {
    "psr-4": {
      "App\\": "src/"
    }
  },
  "autoload-dev": {
    "psr-4": {
      "Tests\\": "tests/"
    }
  }
}

起動して確認します。

docker compose up -d --build
docker compose exec app php -v
docker compose exec app composer --version

詰まり時:

docker compose logs app

3. PHPUnitを導入して最小設定を作る

PHPUnit 11 を導入します。 バージョンを固定しておくと、環境ごとの差分が少なくなり、再現しやすくなります。

docker compose exec app composer require --dev phpunit/phpunit:^11
docker compose exec app vendor/bin/phpunit --version

composer require --dev ... を実行すると、composer.jsonrequire-dev には依存が自動追記されます。

phpunit.xml を作成します。
bootstrap="vendor/autoload.php" を入れて、Composer オートロードを使えるようにします。

<?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="default">
      <directory>tests</directory>
    </testsuite>
  </testsuites>
</phpunit>

詰まり時:

  • vendor/bin/phpunit が見つからない: composer require の実行場所と結果を確認
  • 拡張不足エラー: Dockerfile を見直し、docker compose up -d --build を再実行

4. 失敗する最初のテストを書く

ここでは、あえて失敗する状態を作ります。
対象クラス MessageFormatter は「入力文字列を整形して返す」だけの最小責務にします。

src/MessageFormatter.php を作成します(失敗版)。

<?php
declare(strict_types=1);

namespace App;

final class MessageFormatter
{
    public function format(string $name): string
    {
        return "Hello, {$name}";
    }
}

この実装はテスト側の期待値 Hello, Taro!(末尾に ! あり)を満たさないため、次の実行で失敗します。

tests/MessageFormatterTest.php を作成します。

<?php
declare(strict_types=1);

namespace Tests;

use App\MessageFormatter;
use PHPUnit\Framework\TestCase;

final class MessageFormatterTest extends TestCase
{
    public function testFormatReturnsGreetingWithExclamation(): void
    {
        $formatter = new MessageFormatter();

        $actual = $formatter->format('Taro');

        $this->assertSame('Hello, Taro!', $actual);
    }
}

失敗を確認します。

docker compose exec app vendor/bin/phpunit

PHPUnit失敗画面

失敗出力の要点:

  • ExpectedActual の差分が表示される
  • 今回は ! が不足していることが原因

詰まり時:

  • テストが検出されない: phpunit.xmltestsuite パスを確認
  • Class "App\MessageFormatter" not found: docker compose exec app composer dump-autoload を実行

5. 実装を修正してテストを成功させる

src/MessageFormatter.php を修正版に更新します。

<?php
declare(strict_types=1);

namespace App;

final class MessageFormatter
{
    public function format(string $name): string
    {
        return "Hello, {$name}!";
    }
}

再実行して成功を確認します。

docker compose exec app vendor/bin/phpunit

PHPUnit成功画面

成功判定:

  • OK が表示される
  • 1 test, 1 assertion が表示される

詰まり時:

  • まだ失敗する: Expected / Actual の値を見比べる
  • 変更が効かない: ファイル保存漏れと実行場所を確認

6. 任意: composer test で実行を短縮する

主線は vendor/bin/phpunit のままですが、任意でショートカットを追加できます。
ch2 の composer.json を次の最終版に更新します。

{
  "name": "example/phpunit-first-test-demo",
  "type": "project",
  "require": {},
  "require-dev": {
    "phpunit/phpunit": "^11"
  },
  "autoload": {
    "psr-4": {
      "App\\": "src/"
    }
  },
  "autoload-dev": {
    "psr-4": {
      "Tests\\": "tests/"
    }
  },
  "scripts": {
    "test": "phpunit"
  }
}

実行例:

docker compose exec app composer test

composer testphpunit だけを書けるのは、Composer scripts 実行時に vendor/bin が PATH に追加されるためです。

詰まり時:

  • composer test が失敗する: scripts.test のキー名とコマンド文字列を確認

7. 詰まりを手順ごとに再確認する

各章の個別導線を補完する横断チェックです。
迷ったら、次のコマンドを順に確認します。

docker compose exec app pwd
docker compose exec app ls -la vendor/bin
docker compose exec app php -m
docker compose exec app composer dump-autoload
docker compose exec app vendor/bin/phpunit --version

見るポイント:

  • vendor/bin/phpunit が存在するか
  • 必要拡張が読み込まれているか
  • オートロード再生成で解決する問題か

復旧不能時は、2章の最小構成から作り直してください。

8. まとめと次の一歩

本記事で、次の状態を達成しました。

  • PHPUnit 11 を最小構成で導入できる
  • 最初のテスト1本を失敗 -> 修正 -> 成功まで通せる
  • vendor/bin/phpunit を主線に継続運用できる

実行不能になった場合は3章へ、失敗内容の読み直しは4章/5章へ戻って確認してください。

次の一歩:

  1. テストケースを2〜3本に増やし、MessageFormatter の境界条件を追加する
  2. リファクタ前後でテストが安全網として機能することを確認する
  3. CI(GitHub Actions)で composer test を自動実行する記事へ進む

シリーズ 1/1

このシリーズ

PHPのテストと品質改善

  1. 1. PHPUnit入門(最初のテスト1本) 現在の記事