本記事について
当サイトを閲覧いただきありがとうございます。 本記事はシリーズ『生成AI時代のアーキテクチャ超入門』の「開発運用アーキテクチャ」カテゴリ第6弾として、テスト設計について解説する記事です。
テストのゴールはカバレッジの数字ではなく「壊れたときに5分で原因が掴めること」本記事ではテストピラミッド・カバレッジ目標値・TDD・flaky対策、「カバレッジ何%狙うか」「E2Eはどこまで書くか」「CIで何を走らせるか」といった実務判断を扱います。
このカテゴリの他の記事
テストは3つの責務で分けて考える
テストは「ユニット」「結合」「E2E」という階層で整理するのが業界の定石ですが、階層名を覚えるよりそれぞれが何を保証する責務かを掴んだ方が判断に効きます。
| 階層 | 責務(何を保証するか) | 代表ツール |
|---|---|---|
| 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年に提唱した図)は、テストを「速く・数が多い・下に積む」「遅く・数が少ない・上に積む」で整理したモデルです。現時点でもテスト戦略の出発点として鉄板の考え方です。
flowchart TB
E2E["E2E Test (10%)<br/>遅く・壊れやすい<br/>ユーザー動線のみ"]
INT["Integration Test (20%)<br/>DB・外部API込みの検証"]
UNIT["Unit Test (70%)<br/>秒で終わる・大量に書く"]
E2E --> INT --> UNIT
LEFT[速度: 遅<br/>本数: 少] -.- E2E
UNIT -.- RIGHT[速度: 速<br/>本数: 多]
classDef e2e fill:#fee2e2,stroke:#dc2626;
classDef int fill:#fef3c7,stroke:#d97706;
classDef unit fill:#dcfce7,stroke:#16a34a;
class E2E e2e;
class INT int;
class UNIT unit;
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%目標 | 自動生成コード・ライブラリラッパーまで巻き込まれる |
カバレッジはチームの品質センサーとして内部で見る指標で、評価軸や承認ブロッカーに使うと劣化します。PR 承認ではカバレッジ絶対値より「新規コードのカバレッジ」(変更行ベース)を見るのが無難で、codecov の project と patch の使い分けが範例です。
TDD — テストを先に書く流派
TDD(Test Driven Development=テスト駆動開発)は、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 に全部書くとテスト間の依存関係が見えなくなり、中期で破綻します。
AI時代の視点
AI 駆動開発では、テストはAIが生成したコードを人間が検証する関門に格上げされます。AI は1時間で数百行を書きますが、その品質を人間が全部読むのは非現実的で、テストだけが自動検証の唯一の砦になります。テストが薄いチームが AI 開発を導入すると、バグが高速で本番に流れる装置が完成してしまいます。
| AI時代に有利 | AI時代に不利 |
|---|---|
| テスト先行(TDD)でAIに仕様を渡す | 「動いたらOK」運用・テスト後回し |
| Jest/Vitest/pytest(AIの学習データ豊富) | 独自テストフレームワーク・社内ラッパー |
| Testcontainers で本番同等 DB 検証 | DB モックで「SQLが通らない」を見逃す |
| 型検査 + カバレッジ + ブランチテスト | ラインカバレッジ90%だけで安心する文化 |
| AI生成テストの人間レビュー文化 | AI が書いたテストを無条件マージ |
AI にテストから書かせ、実装はそのテストを通すように書かせるのが TDD の新しい形です。これができるチームは生成スピードが圧倒的に伸び、できないチームは品質事故の頻度が上がります。
AI 時代はテストが仕様書、型が設計書。これが読めないチームに AI は導入不能です。
よくある勘違い①
テストに関する誤解は、現場で頻繁に事故を生みます。
カバレッジ80%あればバグは出ない
カバレッジは実行したかしか測りません。if (x > 0) doA() else doB() を「xが正」でしか通していなくても、ラインカバレッジは100%です。ブランチカバレッジとアサーション(検証の中身)がなければ、数字は飾りです。
E2Eを厚くすればユーザー体験を保証できる
E2E は画面越しのシナリオしか見ていません。視覚バグ(CSS 崩れ・フォント切れ)やアクセシビリティは別の仕組み(Visual Regression・axe-core)で検証します。
よくある勘違い②
テストを書く時間がない
テストを書かない時間の方が長くなるのが現場の現実です。デバッグ・本番障害対応・顧客説明で溶ける時間の方が、テスト書く時間より重い。テストは未来の時間の前借りで、前借りを拒否したチームほど利息で溶けます。
TDDは理想論で現場では機能しない
全機能にTDDは現実的ではないのは確かですが、ドメインロジック中核(料金計算・権限判定・在庫操作)には TDD が圧倒的に効く。UI 配線は後書きで十分、というバランスが実用解です。
テストは書かない時間の方が高くつく負債です。
決めるべきこと — あなたのプロジェクトでの答えは?
以下の項目について、あなたのプロジェクトの答えを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軸に切り替え、絶対値のカバレッジ目標は撤廃しました。結果としてテストの質が戻り、同時にゲッターセッターのゴミテストが大量にリネーム・削除されたという、示唆的な顛末です。
テストの価値はバグを減らした実績でしか測れません。
最終的な判断の仕方
テスト設計の本質は、壊れても気づける仕組みを作ることで、「壊れないこと」を目指すことではないという発想です。100% のバグ予防は不可能で、気づくまでの時間が短いチームほど事業リスクが小さくなる。だから設計の核は、「Unitで速く広く、Integrationで本番同等、E2Eで動線だけ、フレークは即隔離」という責務の切り分けです。
現時点の鉄板は「テストピラミッド(Unit 70/Integration 20/E2E 10)+ Testcontainers + ブランチカバレッジ + フレーク即隔離 + ドメイン中核のみTDD」カバレッジ目標は内部指標として**ドメイン中核80%/それ以外60%**の2段で運用し、KPI・承認ブロッカーには使わない。AI 駆動開発では、テストがAIに仕様を渡す唯一の機械可読手段になるため、テストが貧弱なチームは AI の恩恵ごと失います。
選定の優先順位
- テストピラミッドの形を先に決める — 逆三角形になっていないか確認
- Testcontainersで本番同等のIntegration環境を作る
- ブランチカバレッジ + 変更行カバレッジを運用指標に
- フレーク隔離フローと「retryは通過じゃない」文化を敷く
「テストは未来の時間の前借り」書かない時間の方が、確実に長くなります。
まとめ
本記事はテスト設計について、テストピラミッド・カバレッジ・TDD・フレーク対策・Testcontainers・モック境界・AI時代のテスト先行まで含めて解説しました。如何だったでしょうか。
テストピラミッドを意識し、Testcontainersで本番同等のDB検証、ブランチカバレッジ+変更行カバレッジを運用指標に、フレークは即隔離する。これが2026年のテスト設計の現実解です。
次回はCI/CD(パイプライン設計・デプロイ自動化)について解説します。
シリーズ目次に戻る → 『生成AI時代のアーキテクチャ超入門』の歩き方
それでは次の記事も閲覧いただけると幸いです。
📚 シリーズ:生成AI時代のアーキテクチャ超入門(59/89)