06. テスト戦略(単体/統合/E2E)
目的
- 単体/統合/E2E を「量の議論」ではなく、複雑さと価値導線に応じて配分する
- 壊れにくさ(変更耐性)を高めるための最小戦略を確立する
得られる判断能力
- 小規模における「現実的なテスト配分」を説明できる(ピラミッドを盲信しない)
- 価値導線に E2E を割り当て、境界を統合テストで守る設計ができる
- 変更頻度(V)と距離(D)を踏まえて、投資先(単体/統合)を決められる
前提/用語
- 単体テスト: 小さい単位(関数/モジュール)で振る舞いを検証
- 統合テスト: 境界を跨ぐ結合(DB、HTTP、状態管理など)を検証
- E2E: ユーザー導線として価値を提供できることを検証
要点
- E2E は少数精鋭で良いが、価値導線を外してはいけない
- 統合テストは「境界の契約」を守る。変更頻度と D(距離)が大きいほど重要になる
- 単体テストは「コア」の変更耐性を支える。V(変動性)が高い領域ほど意味が出る
S/D/V とテスト投資の接続
章 03 の S/D/V は、テスト配分を「好み」ではなく「根拠のある投資判断」にするための補助線です。以下は目安であり、絶対解ではありません。
- D(距離)が大きいほど、統合テストの価値が上がる
- 遠い境界(例:
UI → API → DB、外部通知 I/F)は、故障時の診断コストが高い - 境界の契約(入力/出力/失敗時の扱い)を統合テストで固定し、運用(ログ/監視)とセットで守る
- 遠い境界(例:
- V(変動性)が高いほど、単体テストの価値が上がる
- 変更が多い領域は、フィードバック速度がボトルネックになりやすい
- 単体テストを「振る舞い(契約)」に寄せると、変更耐性を維持しやすい
- S(統合強度)が高いほど、境界を増やしても同時変更は減りにくい
- 同時変更が前提なら、過剰な分離やモックの増殖は投資対効果が下がる
- 「一体として扱う」粒度でテストし、必要な境界だけ統合で守る
実務では、Appendix B のテンプレを使って「採点 → 配分 → 合意」を固定すると再現性が上がります。
E2E を少数精鋭に保つ設計手順
E2E は価値導線の「証拠」になりますが、増やすほど実行時間と不安定さが増えます。小規模では次の手順で最小セットに落とします。
- 価値導線(ユーザーが達成したい結果)を 3〜5 個に絞る
- 「止まると業務が止まる」導線だけを E2E 候補にする
- E2E の期待値を「安定して観測できるもの」に限定する
- 例: 通知は「メールが届いた」ではなく「通知要求が記録された」などに落とす
- 残りのリスク(境界、例外系、権限)は統合/単体に移す
タスク管理の最小セット例:
- 管理者がタスクを作成し、担当者に割り当てる(権限・永続化・主要導線)
- 担当者がタスクを完了する(状態遷移・権限・永続化)
- 期限超過の表示(重要なら。表示の観測は E2E 1 本でよい)
成果物駆動: B-3(仕様)→B-8(配分)への落とし方
テスト配分は「好き嫌い」ではなく、仕様(Behavior)の成果物から機械的に決めるとブレません。
手順(最小):
- B-3 の受け入れ条件(Given/When/Then)を列挙し、価値導線として E2E 候補を抽出する
- B-3 の例外系/失敗時の振る舞いを列挙し、境界の契約として統合テスト候補にする
- B-3 の観測点を見直し、E2E の期待値を「安定して観測できるもの」へ落とす(外部I/F到達性を直接検証しない)
- 残りの純粋ロジック(ルール/状態遷移/期限判定)を単体へ寄せる
ランニング例(割り当て + 通知)の割り当て例(抜粋):
- E2E: 割り当て操作の成功と UI 表示の更新(通知は「通知要求が記録された」等の観測点に落とす)
- 統合:
403/404/409等のエラー契約、永続化、通知失敗時の扱い(検知/再送起点) - 単体: 権限ルール、割り当て可否、期限・状態遷移などの純粋ロジック
任意: 主要要件について「どの仕様/テストで担保するか」を表で残すと、配分の漏れ(未検証)や過剰(E2Eの増殖)をレビューで検知しやすくなります。
- トレーサビリティ(要件→仕様→設計→テスト): Appendix B(B-11) / Appendix D(D-19)
- 受け入れ条件(AC)ID: Appendix B(B-12) / Appendix D(D-20)
フレーク対策(時刻/データ/外部I/O/待機)
フレーク(不安定)は「テストの問題」ではなく「設計と運用の不確実性」が表面化したものです。原因別に対策を固定します。
時刻(Time)
- アンチパターン:
Date.now()等のグローバル参照をロジック内に埋め込む - 対策:
- 時刻は引数/
now()として注入し、固定できる形にする(章 05 の方針) - タイムゾーンを固定し、境界(API)での入出力形式を明文化する
- 時刻は引数/
データ(Data)
- アンチパターン: テストが「共有状態(共有DB)」や「実行順」に依存する
- 対策:
- テストごとに状態を初期化する(トランザクションロールバック、テーブルクリア、DB を分離する等)
- 期待値を「順序」ではなく「集合」として検証する(意図しない並び順依存を避ける)
- 競合(同時更新)や整合性制約は、整合性境界と外部挙動(
409等)を先に固定する(参照: Appendix B(B-15) / Appendix D(D-23))
外部I/O(External I/O)
- アンチパターン: 外部サービスを E2E/統合で直接叩く(ネットワーク、レート制限、障害の影響)
- 対策:
- adapter 境界にテストダブルを置き、入力/出力/失敗時の扱いを契約として固定する
- 外部連携の「失敗時の期待」を受け入れ条件に含める(Appendix B-3 のテスト観点)
待機(Wait/Async)
- アンチパターン: 固定の
sleepに頼る、タイムアウト値を場当たりで伸ばす - 対策:
- 「条件が満たされるまで待つ」形にし、上限時間(タイムアウト)と診断ログをセットにする
- 非同期処理(通知など)は、結果を同期的に観測できるポイント(キュー、アウトボックス、ログ)を用意する
例(ランニング例)
タスク管理を例にすると、配分の考え方は次の通りです。
- 単体(厚め):
- 期限判定、状態遷移、入力バリデーション
- 権限判定ロジック(ロール/所有者のルール)
- 統合(境界を守る):
- API + DB(永続化と取得)
- 認可(ミドルウェア/ガード)とユースケースの接続
- 通知 I/F(失敗時の扱い、リトライ)
- E2E(主要導線のみ):
- 管理者がタスクを作成し、担当者に割り当てる
- 担当者がタスクを完了する
- 期限超過の表示/通知(重要なら)
配分の目安(例):
- 単体 20 / 統合 10 / E2E 3(数字は目安。価値導線と複雑さで調整する)
演習(最小1個)
ランニング例の価値導線を 1 つ選び、Appendix B の「テスト配分テンプレ(B-8)」を埋めてください。
追加で余力があれば、章 03 の観点で「なぜその配分にしたか」を 3〜5 行で説明してください(S/D/V のどれが効いているか)。
よくある失敗
- E2E を増やして安心しようとし、実行時間と不安定さで破綻する
- 統合ポイント(API、DB、認可)を単体で誤魔化し、本番で壊れる
- テストが仕様の代替になり、要件の合意が曖昧なまま進む
チェックリスト
- 価値導線(ユーザー価値)に E2E が割り当てられている
- 境界(API/DB/認可)の契約が統合テストで検証されている
- 単体テストがコアの振る舞いを守っている(実装詳細に依存しない)
- フレーク要因(時刻/データ/外部I/O/待機)に対する制御策がある