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-4 に src/ を登録しておくと、後続のテストでクラス解決に詰まりにくくなります。
{
"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.json の require-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

失敗出力の要点:
ExpectedとActualの差分が表示される- 今回は
!が不足していることが原因
詰まり時:
- テストが検出されない:
phpunit.xmlのtestsuiteパスを確認 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

成功判定:
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 test で phpunit だけを書けるのは、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章へ戻って確認してください。
次の一歩:
- テストケースを2〜3本に増やし、
MessageFormatterの境界条件を追加する - リファクタ前後でテストが安全網として機能することを確認する
- CI(GitHub Actions)で
composer testを自動実行する記事へ進む