本記事について
当サイトを閲覧いただきありがとうございます。 本記事はシリーズ『生成AI時代のアーキテクチャ超入門』の「開発運用アーキテクチャ」カテゴリ第6弾として、テスト設計について解説する記事です。
テストのゴールはカバレッジの数字ではなく「壊れたときに5分で原因が掴めること」本記事ではテストピラミッド・カバレッジ目標値・TDD・flaky対策、「カバレッジ何%狙うか」「E2Eはどこまで書くか」「CIで何を走らせるか」といった実務判断を扱います。
本記事のテーマについてさらに詳しく知りたい方は『システム設計のセオリーと実践方法がこれ1冊でしっかりわかる教科書』・『いちばんやさしいClaude Codeの教科書』も参考にしてみてください。
そもそもテスト設計とは何か
車の車検を想像してください。ブレーキ・ライト・排気ガス──項目ごとに検査基準があり、合格しないと公道を走れません。検査なしで走ればいつ事故が起きてもおかしくない状態です。
テスト設計とは、ソフトウェアが正しく動いているかを自動で検証する仕組みをどう構築するかを決めることです。何を・どの粒度で・どのタイミングで検査するかを設計し、コードを変更するたびに自動で確認が走る状態を作ります。
もしテスト設計がなければ、コードを1行変えるたびに「他の機能が壊れていないか」を人力で確認することになり、変更のたびに恐怖が伴います。結果、誰もコードに触れたがらなくなります。
なぜテスト設計が必要か
変更への恐怖をゼロにする
テストがなければ、コードを1行変えるたびに「他の機能が壊れていないか」を手動で確認することになります。自動テストがあれば、変更後に数分でフィードバックが返り、安心してリファクタリングできます。
障害発生時に5分で原因を掴む
テストが壊れた箇所を見れば、どこの変更が問題を起こしたかが即座にわかります。テストがない場合、原因特定に数時間〜数日かかることもあります。
AI生成コードの品質を担保する
AI が書いたコードが正しいかを人間が毎回レビューするのは限界があります。テストで機械的に検証するのが現実的な安全網です。
テストは3つの責務で分けて考える
テストは「ユニット」「結合」「E2E」(End-to-End=ユーザー操作を模倣してシステム全体を通しで検証するテスト)という階層で整理するのが業界の定石ですが、階層名を覚えるよりそれぞれが何を保証する責務かを掴んだ方が判断に効きます。
| 階層 | 責務(何を保証するか) | 代表ツール |
|---|---|---|
| Unit Test | 関数・クラスの論理の正しさ | Jest/Vitest/pytest/JUnit |
| Integration Test | モジュール結合・DB/外部APIとの接続 | Testcontainers/Supertest/pytest + Docker |
| E2E Test | ユーザー操作の画面越しの動線 | Playwright/Cypress |
| Contract Test | サービス間の API 契約の互換性 | Pact/Spring Cloud Contract |
| Performance Test | 負荷・レイテンシの数値の境界 | k6/Gatling/Locust |
契約テスト(Contract Test=呼び出し側と呼ばれ側が合意した API 仕様を機械検証するテスト)はマイクロサービス環境で重要度が上がっていますが、モノリスでは不要です。記事ごとに必要な階層を選ぶ判断も、アーキテクトの仕事になります。
テストピラミッドの比率
テストピラミッド(Mike Cohn が2009年に提唱した図)は、テストを「速く・数が多い・下に積む」「遅く・数が少ない・上に積む」で整理したモデルです。現時点でもテスト戦略の出発点として鉄板の考え方です。
Unit 70/Integration 20/E2E 10 の比率はあくまで目安で、扱うドメインによってズレます。決済・在庫などロジック中心なら Unit 80 まで寄る一方、管理画面中心の CRUD 業務アプリなら Integration が30〜40に寄る。何を最も信頼したいかで比率は決まります。
アンチパターン:アイスクリームコーン
多くの現場で起きているのが、テストピラミッドが逆三角形になっているケースです。E2E が大量にあり、Unit が薄い──これを業界では Icecream Cone Anti-pattern(アイスクリームコーン・アンチパターン)と呼びます。
____________
\ / E2E Test 60% ← 壊れやすく・遅く・メンテ放棄
\--------/
\ / Integration 30%
\----/
\ / Unit Test 10%
\/
起こりがちな理由は、「E2Eの方が安心感がある」「Unit Testは書くのが面倒」という2点。だが E2E は数十秒〜数分かかり、非同期処理でフレーク(flaky=同じコードなのに実行結果が安定しないテスト)しやすく、壊れるたびに誰も直さず skip タグが積まれていきます。気づいたらE2Eが500本あるが半分skip、残り半分も毎日3本は赤いという地獄になります。
E2E を積み上げて安心するのは罠。Unit の薄さを E2E では補えません。
テストで何を走らせるか — 段階別の実務
CI で「全テストを毎回走らせる」のは非現実的です。CI/CD と同じく、コードが触れるタイミングで段階を切り、各段階で走らせる種別と量を変えるのが実務の答えです。
| 段階 | いつ走るか | 何を走らせるか | 目標時間 |
|---|---|---|---|
| ①pre-commit | コミット作成時(ローカル) | 変更ファイルの Lint + 単一テスト | 5秒以内 |
| ②pre-push | push 直前 | 変更範囲の Unit Test | 30秒以内 |
| ③PR作成・更新時 | GitHub に push された時 | 全Unit + 型チェック + 変更範囲Integration | 10分以内 |
| ④merge時 | main にマージされた瞬間 | 全 Integration + スモーク E2E | 20分以内 |
| ⑤ナイトリー | 夜間バッチ | 全 E2E + 負荷試験 + Contract Test | 数時間 |
PR時に全E2Eを走らせるのは筋が悪い選択です。開発速度が露骨に落ち、誰もテストを増やさなくなる。E2E はマージ後のスモーク(最小動線)とナイトリーに分け、PR 段階では Unit + Integration で十分──これが現場で機能している構成です。
カバレッジ目標は何%を狙うか
カバレッジ(coverage=コードのうちテストが実行した割合)は下限ラインとして使うもので、目標値ではありません。80% を狙うのは良いが、90%を追い始めた瞬間に意味のないテストが量産されるのが現場の常です。
| 目標 | 現実的か | コメント |
|---|---|---|
| 40%未満 | 危険 | テストの下地が薄すぎる。リファクタで即死 |
| 60〜70% | 一般的 | 新規プロジェクト・SaaS の典型ライン |
| 80% | 本命 | ドメインロジック中心部は80%を超える設計にする |
| 90%以上 | 要注意 | 儀式化・ゲッターセッターのテスト量産が始まる |
| 100% | 禁じ手 | 達成コストに見合わない。宗教化する |
カバレッジはライン・ブランチ・関数の3種類がありますが、ブランチカバレッジ(if 文の条件分岐を両方通したか)を基準に据えるのが現代の定石です。ラインカバレッジだけ見るとif文の片方しか通っていないのに90%という偽陽性が出やすい。jest --coverage や pytest-cov はブランチカバレッジを出せます。
カバレッジは**ドメインロジックは80%超、それ以外は60%**が現実的な線引きです。
やってはいけないこと
カバレッジは計測が簡単なため、KPI として経営層に報告されがちですが、数字だけを追い始めた瞬間にテストの質が崩壊します。以下は現場で頻発する禁じ手です。
| 禁じ手 | なぜダメか |
|---|---|
| 経営指標にカバレッジ%を採用 | 「カバレッジ上げ PR」でゲッターセッターにテストが生える |
| カバレッジ閾値を PR マージ条件に | しきい値ギリギリの差分が通らず、レビュー以前で詰まる |
| カバレッジ未達を個人評価に紐付け | テスト品質より数字稼ぎの行動が始まる |
| 全ファイル一律80%目標 | 自動生成コード・ライブラリラッパーまで巻き込まれる |
| 「カバレッジ80%あればバグは出ない」と数字信仰 | ラインカバレッジだけでは分岐の片方未通過でも100%になる偽陽性 |
| 「テストを書く時間がない」と先送り | デバッグ・本番障害・顧客説明で溶ける時間の方が確実に長い |
カバレッジはチームの品質センサーとして内部で見る指標で、評価軸や承認ブロッカーに使うと劣化します。PR 承認ではカバレッジ絶対値より「新規コードのカバレッジ」(変更行ベース)を見るのが無難で、codecov の project と patch の使い分けが範例です。
AI判断軸
| AI有利 | AI不利 |
|---|---|
| テスト先行(TDD)でAIに仕様を渡す | 「動いたらOK」運用・テスト後回し |
| Jest/Vitest/pytest(AI学習データ豊富) | 独自テストフレームワーク・社内ラッパー |
| Testcontainers で本番同等DB検証 | DBモックで「SQLが通らない」を見逃す |
| 型検査 + カバレッジ + ブランチテスト | ラインカバレッジ90%だけで安心する文化 |
| AI生成テストの人間レビュー文化 | AIが書いたテストを無条件マージ |
- テストピラミッドの形を先に決める — 逆三角形になっていないか確認
- Testcontainersで本番同等のIntegration環境を作る
- ブランチカバレッジ + 変更行カバレッジを運用指標に
- フレーク隔離フローと「retryは通過じゃない」文化を敷く
テストをAIに書かせる場合の注意点
AIにテストコードを生成させると、正常系のテストは高精度で書けます。しかし以下の問題が頻発します。
- 境界値テストの漏れ(0件・1件・上限値のケースが抜ける)
- 異常系の網羅不足(ネットワークエラー・タイムアウト・権限不足のケースを書かない)
- 実装の内部構造に依存したテスト(リファクタリングで壊れるbrittle test)
- モック過多(本物のDBを使うべき箇所までモックして偽の安心を得る)
AI生成テストは「初稿」として使い、人間がレビューして境界値・異常系を追加する運用が現実的です。テスト仕様を先に人間が書き、実装をAIに任せるTDDスタイルが最も品質が安定します。
テストがAI活用の品質ゲートとして機能する
AIにコード生成を任せる場合、既存のテストスイートが品質の最後の砦になります。AIが書いたコードをpush→CIで全テスト実行→失敗したら差し戻す、という自動フローが成立していれば、AIの生成品質に不安があっても安全に利用できます。
この前提が成立するには、テストスイート自体の信頼性が高い必要があります。フレークテストが多い・カバレッジが低い・モックだらけでIntegrationが薄い状態では、AIが書いた誤ったコードがテストをすり抜ける確率が上がります。
TDD — テストを先に書く流派
TDDは、2000年代に Kent Beck が広めた開発スタイルで、失敗するテストを書く → 通す最低限の実装 → リファクタの3ステップを回します。
| 段階 | やること | 赤/緑 |
|---|---|---|
| Red | 失敗するテストを先に書く | 赤 |
| Green | テストが通る最小限の実装を書く | 緑 |
| Refactor | テストを通したまま設計を整える | 緑 |
TDD の本質は「テストを先に書くこと」ではなく、仕様を最初に言語化してから実装に入るという思考の順序です。実装しながら仕様を考えると「動いたからOK」で終わるのが人間の性で、TDD はその罠を避ける装置として機能します。
ただし、全機能で TDD を回すのは現実的ではありません。ドメインロジック中心部(決済・在庫計算・料金プラン判定)はTDDで、UI配線・CRUDはテスト後書きで十分、というのが現場のバランスです。
フレーク(flaky)テストの鬼門
フレーク(flaky=同じコードなのに、実行のたびに通ったり落ちたりするテスト)は、テストの信頼性を崩壊させる最大の敵です。3回に1回落ちるテストが10本あると、CIの緑色は確率的にしか出なくなり、誰も赤を信じなくなる。赤を無視する文化が根付くと、本物のバグまで見逃す組織に変わります。
| 原因 | 対処 |
|---|---|
時刻に依存(Date.now()) | テスト内で時刻を固定(jest の fakeTimers) |
| 非同期のタイミング依存 | 明示的な waitFor / expect.poll で待つ |
| テスト間の状態共有 | 各テストで DB・キャッシュを必ずクリーン |
| ネットワーク経由の外部 API 呼び出し | モック化 or Testcontainers で隔離 |
| 乱数・UUID に依存 | seed 固定、または契約を緩める |
フレークテストは即座に隔離・修正・削除のいずれかを選ぶのが鉄則です。「また落ちたか、retryで通ったからOK」という運用は、retry機能そのものが事故の温床です。Jest・Vitest・Playwright に retry 機能はあるものの、「retryで通ったテストは通っていないのと同じ」という扱いにするのが、真っ当なチームの運用です。
retryで通すテストは、通っていない。隔離か修正の二択です。
テスト用DBとTestcontainers
結合テストで最も事故るのがDB接続です。モック(mock=本物の代わりに用意した偽物)でDBを置き換えると「SQL は合ってるはずなのに本番で落ちる」が頻発し、ローカルに立てたSQLiteで代用すると PostgreSQL/MySQL との方言差でバグが見つからない。
現時点の鉄板は Testcontainers です。これは Docker コンテナで本物のDB・Redis・Kafkaをテスト時に起動し、終わったら破棄するライブラリで、「本番と同じミドルウェアで結合テストを走らせる」を実現します。
| 方法 | 本番との差 | 速度 | 推奨度 |
|---|---|---|---|
| モック(DB接続そのものを置換) | 大(SQL を検証できない) | 極速 | SQL のバグが漏れる |
| ローカル SQLite 代替 | 中(方言差) | 速 | 小規模のみ |
| 共有ステージング DB | 小 | 中 | テスト並列化不可 |
| Testcontainers | ほぼゼロ | 中(初回のみ遅い) | 本命 |
「DBの方言まで含めて検証する」は、カバレッジ数字を上げるより100倍価値のある投資です。
モックの使いどころ — 壊すなモックvs使うべきモック
モックは便利だが過剰に使うとテストの意味が消える道具です。境界を間違えると、「テストは全緑だが本番は壊れている」という最悪の状態になります。
- モックすべき対象:外部SaaS(Stripe・SendGrid・Slack)、時刻・乱数、コストの高い計算
- モックしてはいけない対象:自プロジェクトのDB、自分たちが書いたコード、同一プロセス内のモジュール
典型的な失敗は「DBアクセスをリポジトリ層でモック」してしまうパターン。こうすると SQL のバグが一切検出されないまま本番に流れます。同様に、「ユースケース層のテストで隣のユースケースをモック」するのも筋が悪い選択で、結合度を可視化する機会を失います。
自分のコードはモックしない、外部世界だけモックする──これが経験則としての鉄則です。
コントラクトテスト(契約テスト)
マイクロサービス・複数チームで API を共有するとき、呼び出し側と呼ばれ側のAPI仕様のズレが本番事故の主要因になります。Contract Test は、呼び出し側が期待する契約と、呼ばれ側が実際に返す仕様が一致しているかを機械検証する仕組みです。
| 方式 | 代表ツール | 特徴 |
|---|---|---|
| Consumer-Driven Contract | Pact | 呼び出し側が契約を書き、呼ばれ側が検証 |
| Spec-First(OpenAPI 契約検証) | Dredd/Prism | OpenAPI 仕様に対してレスポンスを検証 |
| Spring系 | Spring Cloud Contract | JVM 環境での決定版 |
モノリス・単一チームでは不要ですが、マイクロサービスやBFF(Backend-for-Frontend=フロント専用の API 集約層)を2チーム以上で運用し始めた瞬間に導入検討のラインが立ちます。人の時間を溶かす調整コストを機械検証で置き換える、という投資です。
テストデータの設計
テストでもう一つ軽視されがちなのがテストデータの作り方です。user_01・product_01 のような無味乾燥な固定データで埋めていくと、テストが増えた時に「これ何のケースをテストしてたっけ」が分からなくなります。
| 手法 | 内容 | 向いているケース |
|---|---|---|
| Fixture(固定データファイル) | YAML/JSON で決まったデータを投入 | 小規模・読み物の多いアプリ |
| Factory(ファクトリ関数) | userFactory({role: 'admin'}) で生成 | 中〜大規模(本命) |
| Builderパターン | チェインで組み立て | 複雑なエンティティ |
| Faker(ランダム生成) | 氏名・住所を自動生成 | 負荷試験・大量データ |
Factory パターン(FactoryBot・fishery・@mikro-orm/seeder 等)が現代の鉄板です。「意味のある差分だけを書き、残りはデフォルト」のスタイルにすると、テストが仕様書として読める形になります。fixture に全部書くとテスト間の依存関係が見えなくなり、中期で破綻します。
決めるべきこと — 自分のプロジェクトでの答えは?
以下の項目について、自分のプロジェクトの答えを1〜2文で言語化してみてください。曖昧なまま着手すると、必ず後から「なぜそう決めたんだっけ」が問われます。
- テストピラミッドの比率目標(Unit/Integration/E2E)
- カバレッジ目標(全体%・ドメイン中核%・ブランチカバレッジ採用有無)
- PR時CIで走らせる範囲(変更範囲のみ or 全Unit)
- E2Eの実行タイミング(merge 後スモーク + ナイトリー)
- テストDB戦略(Testcontainers/モック/共有DB)
- フレークテストの扱い方(隔離→修正→削除のフロー)
- Contract Testの導入要否(マイクロサービス/BFF 化の有無で判断)
- テストデータの作り方(Factory/Fixture/Builder)
筆者メモ — カバレッジKPI化が壊したチーム
ある中規模 SaaS 企業で「カバレッジ80%をチームKPIに設定」したところ、3か月でゲッターセッターのテストが数千行増え、一方で本質的なドメインロジックのバグは減らなかった──という事例が業界では知られています。数字は簡単に上がるが、品質は変わらない。なぜなら「カバレッジを上げる」ことと「バグを減らす」ことは別物だからです。
その後このチームは KPI を「新規バグの本番流出件数」と「PR差分のカバレッジ(変更行ベース)」の2軸に切り替え、絶対値のカバレッジ目標は撤廃しました。結果としてテストの質が戻り、同時にゲッターセッターのゴミテストが大量にリネーム・削除されたという、示唆的な顛末です。
テストの価値はバグを減らした実績でしか測れません。
この記事に関連する記事
まとめ
本記事はテスト設計について、テストピラミッド・カバレッジ・TDD・フレーク対策・Testcontainers・モック境界・AI時代のテスト先行まで含めて解説しました。如何だったでしょうか。
テストピラミッドを意識し、Testcontainersで本番同等のDB検証、ブランチカバレッジ+変更行カバレッジを運用指標に、フレークは即隔離する。これが2026年のテスト設計の現実解です。
次回はCI/CD(パイプライン設計・デプロイ自動化)について解説します。
シリーズ目次に戻る → 『生成AI時代のアーキテクチャ超入門』の歩き方
本記事で扱った内容の詳細は JUnit 5 公式ドキュメント も合わせて参考にしてください。
それでは次の記事も閲覧いただけると幸いです。
📚 シリーズ:生成AI時代のアーキテクチャ超入門(59/89)

