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 は価値導線の「証拠」になりますが、増やすほど実行時間と不安定さが増えます。小規模では次の手順で最小セットに落とします。

  1. 価値導線(ユーザーが達成したい結果)を 3〜5 個に絞る
  2. 「止まると業務が止まる」導線だけを E2E 候補にする
  3. E2E の期待値を「安定して観測できるもの」に限定する
    • 例: 通知は「メールが届いた」ではなく「通知要求が記録された」などに落とす
  4. 残りのリスク(境界、例外系、権限)は統合/単体に移す

タスク管理の最小セット例:

  • 管理者がタスクを作成し、担当者に割り当てる(権限・永続化・主要導線)
  • 担当者がタスクを完了する(状態遷移・権限・永続化)
  • 期限超過の表示(重要なら。表示の観測は E2E 1 本でよい)

成果物駆動: B-3(仕様)→B-8(配分)への落とし方

テスト配分は「好き嫌い」ではなく、仕様(Behavior)の成果物から機械的に決めるとブレません。

手順(最小):

  1. B-3 の受け入れ条件(Given/When/Then)を列挙し、価値導線として E2E 候補を抽出する
  2. B-3 の例外系/失敗時の振る舞いを列挙し、境界の契約として統合テスト候補にする
  3. B-3 の観測点を見直し、E2E の期待値を「安定して観測できるもの」へ落とす(外部I/F到達性を直接検証しない)
  4. 残りの純粋ロジック(ルール/状態遷移/期限判定)を単体へ寄せる

ランニング例(割り当て + 通知)の割り当て例(抜粋):

  • E2E: 割り当て操作の成功と UI 表示の更新(通知は「通知要求が記録された」等の観測点に落とす)
  • 統合: 403/404/409 等のエラー契約、永続化、通知失敗時の扱い(検知/再送起点)
  • 単体: 権限ルール、割り当て可否、期限・状態遷移などの純粋ロジック

任意: 主要要件について「どの仕様/テストで担保するか」を表で残すと、配分の漏れ(未検証)や過剰(E2Eの増殖)をレビューで検知しやすくなります。

フレーク対策(時刻/データ/外部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/待機)に対する制御策がある