はじめに
この連載は、PHPは書けるけれどテストコードは書いたことがない、という方に向けた入門シリーズです。素のPHPとPHPUnitで基礎を固め、後半ではLaravelでのテストまで扱います。
「テストが大事なのはわかっているけれど、何から始めればいいかわからない」「書いてみたけれど、効果が実感できなかった」。そんな方が、実務で自信を持ってテストを書けるようになることを目指します。
第1回の今回は、コードはほとんど書きません。次の3つを理解して、テストを書く「理由」をはっきりさせることが目的です。
- 手動の動作確認だけでは、なぜ不十分なのか
- テストコードがあると、開発の何が変わるのか
- テストにはどんな種類があり、どう組み合わせるのがよいのか
理由がわかっていると、第2回以降で出てくるテクニックの一つひとつが「なぜこう書くのか」と結びつき、ぐっと理解しやすくなります。
手動の動作確認には限界がある
テストコードを書かない場合、動作確認はブラウザで画面を操作したり、var_dump() で値を表示したりして、人の目で行います。小さなプログラムならそれで十分です。しかし、コードが育っていくにつれて、次の3つの壁にぶつかります。
壁1:確認の手間が、機能の数だけ増えていく
ECサイトのカートを例に考えてみましょう。最初は「商品を入れたら合計金額が出る」だけでした。そこに数量、消費税、セール割引、在庫チェックと機能が増えていきます。
新しい機能を1つ追加したとき、本当に確認すべきなのは新機能だけではありません。既存の機能が壊れていないかも確認する必要があります。機能が10個あれば、変更のたびに10個分の確認が必要です。やがて「たぶん大丈夫だろう」と確認を省くようになり、そこからバグが入り込みます。
壁2:人は同じ確認を正確に繰り返せない
手動の確認は、毎回少しずつ手順や入力値が変わります。前回は数量を3にして試したのに、今回は1で試してしまった。それだけで、数量の計算に入ったバグを見逃します。
実際にこの連載の第2回では、合計金額の計算で「数量を掛け忘れる」バグをわざと入れてみます。数量1で確かめている限り、このバグは表に出ません。
壁3:そもそも手で再現できない条件がある
第4回では、セール割引の終了判定に次のようなバグが入り込む例を扱います。
- セールは11月30日 23:59:59 まで
- ところが、条件式の書き間違いで、23:59:59 ちょうどだけ定価になってしまう
このバグが影響するのは、1年のうちたった1秒間です。この瞬間を狙って手で確認するのは、まず不可能でしょう。
端数処理も同様です。第3回では、「税込金額は1円未満を切り捨て」の仕様なのに、四捨五入してしまうバグを扱います。105円の商品を買ったときだけ1円ずれる、といった種類のバグは、よほど意識して入力値を選ばない限り、画面を触っていても気づけません。
手動の確認がダメだということではありません。見た目や使い心地の確認は、人の目でしかできません。ただ、「計算が正しいか」「条件分岐が正しいか」のように、正解がはっきり決まっている確認は、人よりプログラムのほうがはるかに得意です。
テストコードがあると何が変わるか
テストコードとは、「この入力なら、この結果になるはず」という確認を、プログラムとして書いておくものです。たとえば、第2回で最初に書くテストはこれだけです。
public function test_数量を指定すると単価×数量が合計金額になる(): void
{
$cart = new Cart();
$cart->add('りんご', 150, 3);
$cart->add('みかん', 80, 2);
$this->assertSame(610, $cart->total());
}
150円のりんごを3個、80円のみかんを2個入れたら、合計は610円になるはず。そう書いてあるだけです。細かい書き方は第2回で説明するので、今は雰囲気がつかめれば十分です。
こうしたテストを書いておくと、開発に次の4つの変化が起きます。
1. 全機能の確認が、コマンド1つ・数秒で終わる
書いたテストは、コマンド1つで何度でも、まったく同じ手順で実行できます。第4回を終えた時点で、この連載のテストは15件になります。それらをすべて実行しても、1秒もかかりません。
............... 15 / 15 (100%)
OK (15 tests, 18 assertions)
変更のたびに全機能を確認できるので、「前に直したはずのバグが、別の修正で復活していた」という事態(デグレード)を防げます。
2. コードを直すのが怖くなくなる
動いているコードを読みやすく書き直すこと(リファクタリング)は、本来とても大切な作業です。しかし、テストがないと「書き直して壊れたらどうしよう」と手が止まり、読みにくいコードがそのまま残り続けます。
テストがあれば、書き直した後に実行して、すべて通れば「振る舞いは変わっていない」と確認できます。第3回では、税込計算の実装を round() から整数計算に書き換えますが、テストが通ることで安心して変更できることを体験します。
3. テストが「動く仕様書」になる
テストの名前を工夫しておくと、実行結果がそのまま仕様の一覧になります。第3回で作るテストを実行すると、次のように表示されます。
Cart (Tests\Cart\Cart)
✔ 空のカートの合計金額は0円
✔ 数量を指定すると単価×数量が合計金額になる
✔ 単価がマイナスなら例外を投げる
✔ 数量が0なら例外を投げる
✔ 税抜0円の税込金額は0円
✔ 税抜100円の税込金額は110円
✔ 税抜105円の税込金額は115円
✔ 税抜999円の税込金額は1098円
WordやExcelの仕様書は、コードを変えても更新し忘れれば古いままです。テストは、コードと食い違えば失敗して知らせてくれるので、常に最新の状態が保たれます。
4. コードの設計が良くなる
テストを書こうとすると、「このクラスは、現在時刻に依存していてテストしにくい」といった設計上の問題に早く気づけます。第4回では、テストしにくいコードをテストしやすい形に作り変えます。その結果、コードは部品の差し替えがしやすく、変更に強い形になります。
テストしやすいコードは、多くの場合、良い設計のコードでもあるのです。
テストの種類とテストピラミッド
ひと口にテストといっても、確かめる範囲によっていくつかの種類があります。代表的なのは次の3つです。
| 種類 | 確かめる範囲 | 例 | 実行速度 |
|---|---|---|---|
| ユニットテスト | 1つのクラスやメソッド | Cart の合計金額の計算が正しいか | とても速い(1件あたり数ミリ秒) |
| 統合テスト | 複数のクラスや、DBなどの外部との組み合わせ | 注文データがDBに正しく保存されるか | やや遅い |
| E2Eテスト | ユーザーの操作から画面表示まで、システム全体 | ブラウザで商品をカートに入れ、購入完了画面まで進めるか | 遅い |
E2Eは「End to End(端から端まで)」の略です。範囲が広いテストほど、実際の使われ方に近い確認ができます。その一方で、実行に時間がかかり、画面のちょっとした変更でも壊れやすくなります。
テストピラミッド
それぞれのテストを、どのくらいの割合で書けばよいのでしょうか。その目安としてよく知られているのが テストピラミッド という考え方です。

速くて安定しているユニットテストを土台として一番多く書き、統合テストは要所に、E2Eテストは重要な操作の流れに絞って少なく書く、というバランスを表しています。
逆の形(E2Eテストばかりで、ユニットテストが少ない)になると、テストの実行に何十分もかかり、ちょっとした画面の変更でテストが大量に壊れます。やがて誰もテストを実行しなくなり、せっかく書いたテストが役に立たなくなってしまいます。
この連載で扱う範囲
この連載では、ピラミッドの土台であるユニットテストを中心に据えます。
- 第2〜5回:ユニットテストの基礎(書き方、テストしやすい設計、テストダブル)
- 第6回:DBを使った統合テスト
- 第7・8回:LaravelでのHTTPテストなど、フレームワークの機能を使ったテスト
E2Eテストは専用のツール(ブラウザを自動操作するものなど)を使う別の分野になるため、この連載では扱いません。まずは土台をしっかり固めることが、テストを長く活かす一番の近道です。
【画像:上のテストピラミッドの図を画像にして、ブログに差し込む】
テストについてのよくある誤解
テストを書き始める前に、よく耳にする誤解を3つ解いておきましょう。
誤解1:テストを書くと、開発が遅くなる
テストを書く時間がかかるのは事実です。ただし、それは「後で払うはずだった時間」を前払いしているだけです。
テストがない場合、変更のたびに手動で確認する時間、本番で見つかったバグを調べる時間、「どこを直すと何が壊れるかわからない」と悩む時間がかかり続けます。こうした時間は、コードが大きくなるほど増えていきます。テストを書く時間は最初こそかかりますが、書いたテストは何度でもタダで再実行できます。
誤解2:テストがあれば、バグはなくなる
テストが確かめられるのは、テストに書いたことだけです。確かめていないケースにバグがあれば、テストがすべて通っていても、バグは残ります。
第3回では、「0円」と「端数なし」のケースだけでは見逃してしまう端数処理のバグが登場します。テストを書くときは、「どの入力を試せばバグが見つかるか」を考えることが大切です。この連載では、そのための考え方(境界値など)も紹介していきます。
誤解3:コードをすべてテストで網羅しなければならない
テストが本番コードのどれだけの行を実行したかを表す「カバレッジ」という指標があります。この数値を100%にすることを目標にしてしまうのも、よくある落とし穴です。
数値を上げること自体が目的になると、「実行はしているが、結果を何も確かめていないテスト」が量産されがちです。大切なのは、壊れたら困る部分を、意味のある形で確かめることです。カバレッジとの付き合い方は、第9回で詳しく扱います。
連載の全体像
この連載は全10回で、3部構成です。連載を通して、ECサイトの「カート」を題材に、少しずつ機能とテストを育てていきます。
| 回 | タイトル | 主な内容 |
|---|---|---|
| 第1回 | なぜテストを書くのか | 手動確認の限界、テストの種類、テストピラミッド(今回) |
| 第2回 | PHPUnitの環境構築と最初のテスト | Composerでの導入、phpunit.xml、テストの実行と失敗の読み方 |
| 第3回 | 読みやすいテストの書き方 | AAAパターン、アサーション、例外のテスト、データプロバイダ |
| 第4回 | テストしやすいコードの書き方 | 依存性注入(DI)、時刻の固定、境界のテスト |
| 第5回 | テストダブル | スタブ・モック・フェイクの違いと使い分け |
| 第6回 | DBを使うテスト | テストデータの準備と後片付け |
| 第7回 | Laravelのテスト基盤 | Unit と Feature、HTTPテスト、Factory |
| 第8回 | Laravelのフェイク機能とPest | メールやキューのフェイク、Pestの紹介 |
| 第9回 | カバレッジとCI | カバレッジの計測と付き合い方、GitHub Actionsでの自動実行 |
| 第10回 | TDDとレガシーコード | テスト駆動開発の実演、既存コードへのテスト導入 |
第1部(第2〜6回)は素のPHPとPHPUnitで基礎を固めます。第2部(第7・8回)では、そこで身につけた考え方をLaravelに持ち込みます。第3部(第9・10回)は、実務でテストを続けていくための実践編です。
読み進めるのに必要なもの
- PHPの基本的な文法(クラス、名前空間、型宣言)がわかること
- ターミナルでコマンドを実行できること
- PHP 8.4以上とComposerが使える環境(第2回で準備します)
記事中のコードと実行結果は、すべて実際に動かして確認しています。
まとめ
- 手動の動作確認は、機能が増えるほど手間が増え、同じ確認を正確に繰り返せず、再現できない条件もある
- テストコードがあると、全機能の確認が数秒で終わり、安心してコードを直せ、テストが動く仕様書になり、設計も良くなる
- テストにはユニット・統合・E2Eの3種類があり、速いユニットテストを土台にしたピラミッド型のバランスがよい
- テストはバグをゼロにする魔法ではなく、カバレッジ100%も目的ではない。壊れたら困る部分を、意味のある形で確かめる
次回予告
第2回は「PHPUnitの環境構築と最初のテスト」です。PHPとComposerの準備からPHPUnitの導入、最初のテストを書いて実行するところまでを、手順どおりに進めていきます。わざとバグを入れて、テストが失敗する様子も体験します。
参考資料
- 第1回:なぜテストを書くのか(今回)
- 第2回:PHPUnitの環境構築と最初のテスト
- 第3回:読みやすいテストの書き方
- 第4回:テストしやすいコードの書き方
- 第5回:テストダブル
- 第6回:DBを使うテスト
- 第7回:Laravelのテスト基盤
- 第8回:Laravelのフェイク機能とPest
- 第9回:カバレッジとCI
- 第10回:TDDとレガシーコード (10/7 公開予定)
Analyzegear

