はじめに
今回のゴールは、PHPUnitを導入して、自分で書いたクラスのテストを1本動かすことです。前回は「なぜテストを書くのか」についてお話ししました。今回からいよいよ手を動かしていきます。
この回を読み終えると、次のことができるようになります。
- Composerで新しいプロジェクトを作り、PHPUnitを導入できる
src/とtests/を分けたディレクトリ構成とオートロードを設定できる- テストクラスを書いて、ターミナルから実行できる
- テストが失敗したときの出力を読み、原因を見つけられる
連載を通して、ECサイトの「カート」を題材にします。今回は、商品を追加して合計金額を計算するだけの小さな Cart クラスから始めます。
記事中の実行結果は、すべて下記の環境で実際に動かして取得したものです。
動作確認環境
PHPUnit 13はPHP 8.4.1以上が必須です。お使いのPHPが8.3以下の場合は、後述の「PHP 8.3の人はPHPUnit 12を使う」を参照してください。
| ソフトウェア | 動作確認バージョン | 備考 |
|---|---|---|
| PHP | 8.4.26 | PHPUnit 13は8.4.1以上が必要 |
| Composer | 2.10.3 | PHPのパッケージ管理ツール |
| PHPUnit | 13.3.5 | 2026年9月時点の最新安定版 |
PHPUnit公式サイトによると、2026年9月時点ではPHPUnit 13が現行の安定版です。PHPUnit 12はPHP 8.3以上で動き、2027年2月までバグ修正が提供されます(PHPUnit 12のリリース告知)。
今回扱う基本的な書き方は、PHPUnit 12と13で違いはありません。どちらを使っても、記事のコードはそのまま動きます。
事前準備:PHPとComposerを用意する
まず、ターミナルで次の2つのコマンドを実行してみてください。両方ともバージョンが表示され、PHPが8.4以上なら準備完了です。次の章へ進んでください。
php -v
composer -V
PHP 8.4.26 (cli) (built: Sep 23 2026 11:52:51) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.4.26, Copyright (c) Zend Technologies
with Zend OPcache v8.4.26, Copyright (c), by Zend Technologies
Composer version 2.10.3 2026-08-27 13:34:23
【スクショ:ターミナルで php -v と composer -V を実行し、両方のバージョンが表示された状態】
Macの場合(Homebrew)
Homebrewが入っていれば、次の1行でPHPとComposerの両方をインストールできます。Homebrewでは最新版のPHPが入るので、バージョン要件も満たせます。
brew install php composer
Windowsの場合(WSL2+Ubuntu)
Windowsでは、WSL2上のUbuntuで作業するのがおすすめです。ただしUbuntu 24.04の標準リポジトリのPHPは8.3です。そのままではPHPUnit 13が入らないので、PHP 8.4を提供しているPPAを追加します。
sudo add-apt-repository ppa:ondrej/php
sudo apt update
sudo apt install php8.4-cli php8.4-xml php8.4-mbstring unzip
Composerは公式のダウンロードページの手順に従ってインストールしてください。インストーラの検証用ハッシュは更新されるため、必ず公式ページのコマンドをコピーして使います。
【スクショ:WSLのUbuntuターミナルで php -v を実行し、8.4系が表示された状態】
PHPUnitの実行には
dom・mbstring・xmlなどの拡張が必要です。php -mで一覧を確認できます。Homebrew版にはすべて含まれています。
プロジェクトを作ってPHPUnitを入れる
1. 作業フォルダと composer.json を作る
作業用のフォルダを作り、その中に composer.json を用意します。
mkdir php-test-cart
cd php-test-cart
mkdir src tests
composer.json は次の内容で作成します。composer init で対話的に作ることもできますが、最初は手で書いたほうが中身を理解しやすいです。
{
"name": "example/cart",
"type": "project",
"require": {
"php": ">=8.4"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
}
}
ポイントは autoload と autoload-dev の2つです。
autoload:App\で始まるクラスをsrc/から探す設定です。本番コード用です。autoload-dev:Tests\で始まるクラスをtests/から探す設定です。開発時だけ読み込まれます。
これで、たとえば App\Cart\Cart クラスを src/Cart/Cart.php に置くだけで、require を書かなくても自動で読み込まれるようになります。
2. PHPUnitをインストールする
PHPUnitは開発時だけ使うツールなので、--dev を付けてインストールします。
composer require --dev phpunit/phpunit:^13.3
パッケージのダウンロード状況が表示され、最後に Generating autoload files と出ればインストール完了です。composer.json に require-dev が追加され、vendor/ フォルダと composer.lock が作られています。
【スクショ:composer require の実行後、Generating autoload files まで表示されたターミナル】
正しく入ったか確認しましょう。
vendor/bin/phpunit --version
PHPUnit 13.3.5 by Sebastian Bergmann and contributors.
vendor/bin/phpunitは、このプロジェクトに入ったPHPUnitを実行するコマンドです。PC全体(グローバル)にインストールしたPHPUnitではなく、プロジェクトごとのバージョンを使うのが基本です。
3. ディレクトリ構成
この回の最終的な構成は次のとおりです。本番コードは src/、テストコードは tests/ に、同じフォルダ階層で置くのが基本です。
php-test-cart/
├── composer.json
├── composer.lock
├── phpunit.xml ← PHPUnitの設定ファイル(後で作る)
├── src/
│ └── Cart/
│ └── Cart.php ← テストされる側
├── tests/
│ └── Cart/
│ └── CartTest.php ← テストする側
└── vendor/ ← Composerが管理(Gitには含めない)
src/Cart/Cart.php のテストは tests/Cart/CartTest.php に置く、という対応関係にしておくと、ファイルが増えても迷いません。
最初のテストを書いて動かす
テストされる側:Cartクラス
商品を追加して、合計金額を返すだけのクラスです。src/Cart/Cart.php に作成します。
<?php
declare(strict_types=1);
namespace App\Cart;
final class Cart
{
/** @var list<array{name: string, unitPrice: int, quantity: int}> */
private array $items = [];
public function add(string $name, int $unitPrice, int $quantity = 1): void
{
$this->items[] = [
'name' => $name,
'unitPrice' => $unitPrice,
'quantity' => $quantity,
];
}
public function total(): int
{
$total = 0;
foreach ($this->items as $item) {
$total += $item['unitPrice'] * $item['quantity'];
}
return $total;
}
}
テストする側:CartTest
tests/Cart/CartTest.php に次の内容で作成します。
<?php
declare(strict_types=1);
namespace Tests\Cart;
use App\Cart\Cart;
use PHPUnit\Framework\TestCase;
final class CartTest extends TestCase
{
public function test_空のカートの合計金額は0円(): void
{
$cart = new Cart();
$this->assertSame(0, $cart->total());
}
public function test_商品を1つ追加すると合計金額はその商品の価格になる(): void
{
$cart = new Cart();
$cart->add('りんご', 150);
$this->assertSame(150, $cart->total());
}
public function test_数量を指定すると単価×数量が合計金額になる(): void
{
$cart = new Cart();
$cart->add('りんご', 150, 3);
$cart->add('みかん', 80, 2);
$this->assertSame(610, $cart->total());
}
}
テストクラスを書くときのルールは3つだけです。
- クラス名は
〇〇Testとし、PHPUnit\Framework\TestCaseを継承する - テストメソッドは
testで始まる名前にし、publicにする - メソッドの中で「期待する結果」と「実際の結果」を
assert〇〇()で比べる
assertSame(期待値, 実際の値) は、2つの値が型も含めて完全に一致するかを調べます。引数は「期待値が先」です。逆にすると、失敗時のメッセージで期待値と実際の値が入れ替わって表示され、混乱のもとになります。
テストメソッド名は日本語でも動きます。何を確かめるテストなのかが一目でわかるので、この連載では日本語名を使います。チームの規約で英語が必要なら、
test_empty_cart_total_is_zeroのように書いても構いません。
実行する
テストのフォルダを指定して実行します。
vendor/bin/phpunit tests
PHPUnit 13.3.5 by Sebastian Bergmann and contributors.
Runtime: PHP 8.4.26
... 3 / 3 (100%)
Time: 00:00.001, Memory: 18.00 MB
OK (3 tests, 3 assertions)
【スクショ:vendor/bin/phpunit tests の実行結果。OK (3 tests, 3 assertions) が表示された状態】
出力の見方は次のとおりです。
| 表示 | 意味 |
|---|---|
... | テスト1件ごとに1文字。. は成功 |
3 / 3 (100%) | 実行したテスト数 / 全テスト数 |
OK (3 tests, 3 assertions) | 3件のテストで、3回のアサーションがすべて通った |
これで最初のテストが動きました。
phpunit.xml で設定をまとめる
毎回 tests を指定するのは面倒なので、設定ファイル phpunit.xml をプロジェクト直下に作ります。これがあれば vendor/bin/phpunit だけで実行できます。
落とし穴:自動生成の設定は初心者向けではない
PHPUnitには、設定ファイルを自動生成する vendor/bin/phpunit --generate-configuration というコマンドがあります。便利そうに見えますが、生成される設定はかなり厳しめです。そのまま実行すると、先ほど成功したテストの結果が次のように変わります。
RRR 3 / 3 (100%)
There were 4 PHPUnit test runner warnings:
1) PHP is not configured for development: display_errors should be On, but is
...
There were 3 risky tests:
1) Tests\Cart\CartTest::test_空のカートの合計金額は0円
This test does not define a code coverage target but is expected to do so
...
OK, but there were issues!
Tests: 3, Assertions: 3, PHPUnit Warnings: 4, Risky: 3.
R はRisky(リスクあり)の略で、テスト自体は通ったものの問題が見つかったことを示します。原因は、生成された設定に含まれる次の2つです。
requireCoverageMetadata="true":どのクラスをテストしているか、属性で明示することを全テストに求める設定です。第9回のカバレッジで扱います。warnWhenPhpIsNotConfiguredForDevelopment="true":php.iniが開発向けの設定になっていないと警告を出す設定です。
どちらも実務では役立つ設定です。ただ、テストを始めたばかりの段階では混乱のもとになるので、この連載では最小限の設定から始めます。
最小限の phpunit.xml
プロジェクト直下に次の内容で作成します。
<?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"
cacheDirectory=".phpunit.cache"
colors="true">
<testsuites>
<testsuite name="default">
<directory>tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>src</directory>
</include>
</source>
</phpunit>
| 設定 | 役割 |
|---|---|
bootstrap | テスト実行前に読み込むファイル。Composerのオートローダーを指定する |
cacheDirectory | 実行結果のキャッシュ置き場。Gitには含めない |
colors | 結果を色付きで表示する(成功は緑、失敗は赤) |
testsuites | テストファイルを探すフォルダ |
source | テスト対象のコードがあるフォルダ。カバレッジ計測で使う |
これで、引数なしで実行できるようになります。
vendor/bin/phpunit
PHPUnit 13.3.5 by Sebastian Bergmann and contributors.
Runtime: PHP 8.4.26
Configuration: /home/you/php-test-cart/phpunit.xml
... 3 / 3 (100%)
Time: 00:00.001, Memory: 18.00 MB
OK (3 tests, 3 assertions)
Configuration: の行に phpunit.xml のパスが出ていれば、設定ファイルが読み込まれています。
【スクショ:色付き表示になった実行結果。OK の行が緑背景になっている状態】
最後に、.gitignore に次の2行を追加しておきましょう。
/vendor/
/.phpunit.cache/
わざと失敗させて、出力を読む
テストの真価がわかるのは、失敗したときです。Cart クラスにわざとバグを入れてみましょう。数量を掛け忘れるという、よくあるミスです。
// 変更前
$total += $item['unitPrice'] * $item['quantity'];
// 変更後(バグ入り)
$total += $item['unitPrice'];
実行すると、次のように表示されます。
..F 3 / 3 (100%)
Time: 00:00.002, Memory: 18.00 MB
There was 1 failure:
1) Tests\Cart\CartTest::test_数量を指定すると単価×数量が合計金額になる
Failed asserting that 230 is identical to 610.
/home/you/php-test-cart/tests/Cart/CartTest.php:33
FAILURES!
Tests: 3, Assertions: 3, Failures: 1.
【スクショ:失敗時の実行結果。FAILURES! の行が赤背景になっている状態】
上から順に読むと、次のことがわかります。
..F:3件目のテストが失敗した(Fは Failure)1) Tests\Cart\CartTest::test_数量を…:失敗したテストの名前。日本語名にしておくと、ここで「数量の計算がおかしい」とすぐわかるFailed asserting that 230 is identical to 610.:期待値は610なのに、実際は230だったCartTest.php:33:失敗したアサーションの行番号
230は、150円+80円です。つまり数量が無視されている、と原因まで推測できます。確認できたら、コードを元に戻して再実行し、OK に戻ることを確かめてください。
テストが通らない状態を「レッド」、通る状態を「グリーン」と呼びます。「壊したら赤くなり、直したら緑に戻る」ことを一度体験しておくと、テストへの信頼感がまったく変わります。
覚えておきたい実行オプション
--testdox:テスト結果を仕様書のように表示する
vendor/bin/phpunit --testdox
Cart (Tests\Cart\Cart)
✔ 空のカートの合計金額は 0円
✔ 商品を 1つ追加すると合計金額はその商品の価格になる
✔ 数量を指定すると単価×数量が合計金額になる
日本語のテスト名が、そのままチェックリストになります。なお、数字の前に半角スペースが自動で入ります。表示を細かく整えたい場合は、第3回で紹介する #[TestDox] 属性を使います。
--filter:特定のテストだけ実行する
vendor/bin/phpunit --filter 数量
. 1 / 1 (100%)
OK (1 test, 1 assertion)
テスト名に「数量」を含むものだけが実行されます。テストが増えてきたとき、修正中の箇所に関係するテストだけを素早く実行したい場面で便利です。
composer test で実行できるようにする
composer.json に scripts を追加すると、composer test で実行できます。チームやCIでコマンドを統一するのに役立ちます。
"scripts": {
"test": "phpunit"
}
composer test
よくあるエラーと対処法
Class “〇〇” not found が出る
EEE 3 / 3 (100%)
There were 3 errors:
1) Tests\Cart\CartTest::test_空のカートの合計金額は0円
Error: Class "App\Carts\Cart" not found
E は Error の意味で、テストが最後まで実行できなかったことを示します。アサーションの失敗(F)とは別物です。
この例は use App\Cart\Cart; を App\Carts\Cart と打ち間違えたケースです。クラスが見つからないときは、次の順に確認します。
use文やnamespaceのつづりが正しいか- 名前空間とフォルダ構成が対応しているか(
App\Cart\Cart→src/Cart/Cart.php) composer.jsonのautoloadを変更した後にcomposer dump-autoloadを実行したか
PHP 8.3の人はPHPUnit 12を使う
PHP 8.3で composer require --dev phpunit/phpunit:^13.3 を実行すると、次のエラーでインストールが止まります。
Your requirements could not be resolved to an installable set of packages.
Problem 1
- Root composer.json requires phpunit/phpunit ^13.3 -> satisfiable by phpunit/phpunit[13.3.5].
- phpunit/phpunit 13.3.5 requires php >=8.4.1 -> your php version (8.3.6) does not satisfy that requirement.
最後の行が「PHPUnit 13.3.5はPHP 8.4.1以上が必要だが、あなたのPHPは8.3.6だ」と教えてくれています。PHPを8.4以上に上げるか、PHPUnit 12を指定してください。その場合は composer.json の "php": ">=8.4" も ">=8.3" に変えておきます。
composer require --dev phpunit/phpunit:^12.5
まとめ
今回は、PHPUnitを導入して最初のテストを動かすところまで進みました。
- PHPUnitは
composer require --devでプロジェクトごとに入れ、vendor/bin/phpunitで実行する src/とtests/を分け、autoloadとautoload-devで読み込みを設定する- テストクラスは
TestCaseを継承し、testで始まるメソッドにassertSame(期待値, 実際の値)を書く phpunit.xmlは最小限から始める。自動生成の厳しい設定は後から足せばよい- 失敗時は、テスト名・期待値と実際の値・行番号の3点を読む
次回予告
第3回は「テストの書き方の基本」です。読みやすいテストの型であるAAAパターン、主要なアサーションの使い分け、例外のテスト、同じテストを複数のデータで回すデータプロバイダを扱います。今回の Cart に消費税の計算を加えながら進めます。
参考資料
- 第1回:なぜテストを書くのか
- 第2回:PHPUnitの環境構築と最初のテスト(今回)
- 第3回:読みやすいテストの書き方
- 第4回:テストしやすいコードの書き方
- 第5回:テストダブル
- 第6回:DBを使うテスト
- 第7回:Laravelのテスト基盤
- 第8回:Laravelのフェイク機能とPest
- 第9回:カバレッジとCI
- 第10回:TDDとレガシーコード (10/7 公開予定)
Analyzegear

