テスト設計ガイド
テストの設計・戦略に関する汎用ガイド。特定のテストフレームワークには依存しない。
テストピラミッド
code
/\
/ \ E2E(少)
/────\ ユーザーフロー全体
/ \
/────────\ 統合(中)
/ \ コンポーネント間連携
/────────────\ ユニット(多)
/ \ 関数・クラス単体
原則: 下層ほど多く・高速・安定、上層ほど少なく・遅い・不安定
テストレベルの選択
| レベル | 対象 | 特徴 |
|---|---|---|
| ユニット | 純粋関数、ビジネスロジック、ユーティリティ、状態変換 | 高速、独立、デバッグ容易 |
| 統合 | API エンドポイント、DB 操作、外部サービス連携、コンポーネント間連携 | 実際の連携検証、セットアップ必要 |
| E2E | クリティカルなユーザーフロー、決済・認証 | 最も現実的、遅い、不安定になりやすい |
選択判断フロー
code
単体で検証可能な純粋ロジック?
├→ Yes → ユニットテスト
└→ No → 複数コンポーネントの連携が関わる?
├→ Yes → 統合テスト
└→ No → ユーザーの操作フロー全体を検証?
├→ Yes → E2E テスト
└→ No → ユニットテスト
何をテストすべきか
優先度: 高
- •ビジネスクリティカルなロジック - 決済計算、権限チェック、データ変換
- •エッジケース - 境界値、null/undefined、空配列・空文字
- •過去にバグがあった箇所 - 回帰防止
優先度: 中
- •複雑な条件分岐
- •外部依存のある処理
- •状態管理ロジック
優先度: 低(または不要)
- •単純な getter/setter
- •フレームワークの機能そのもの
- •外部ライブラリの動作そのもの
テストダブルの選択
| 種類 | 目的 | 使用場面 |
|---|---|---|
| Stub | 固定値を返す | 外部 API の戻り値を固定、特定の状態を再現 |
| Mock | 呼び出しを検証 | メソッド呼び出しの確認、引数の検証 |
| Fake | 簡易実装を提供 | InMemory Repository、状態を持つ代替 |
| Spy | 実装を維持しつつ記録 | 実際の処理 + 呼び出し回数の確認 |
選択フロー
code
呼び出しを検証したい?
├→ Yes → 実装も動かしたい?
│ ├→ Yes → Spy
│ └→ No → Mock
└→ No → 状態を持つ必要がある?
├→ Yes → Fake
└→ No → Stub
ベストプラクティス
code
1. 境界でのみダブルを使う(外部システム、時間、乱数) 2. Fake を優先(InMemory 実装は再利用可能、実際の動作に近い) 3. Mock は最小限に(副作用の検証時のみ) 4. 「何が呼ばれたか」より「何が起きたか」に注目
詳細: references/test-doubles.md
テスト設計パターン
AAA パターン
code
Arrange: 準備(データ、依存関係のセットアップ) Act: 実行(テスト対象の呼び出し) Assert: 検証(期待結果との比較)
Given-When-Then
code
Given: 前提条件(システムがこの状態のとき) When: アクション(この操作を行うと) Then: 期待結果(こうなるべき)
使い分け
code
AAA: ユニットテスト向き(技術者視点) Given-When-Then: 統合/E2E テスト向き(ビジネス視点)
テスト命名規則
code
{対象}_{条件}_{期待結果}
例:
calculateTotal_withEmptyCart_returnsZero
login_withInvalidPassword_throwsError
fetchUser_whenNotFound_returnsNull
原則
code
- テスト名だけで何をテストしているか分かる - 条件と期待結果を含める - 実装詳細ではなく振る舞いを記述
テストデータの設計
code
原則: - テストごとに独立したデータを用意 - 共有のテストデータに依存しない - ファクトリ/ビルダーパターンでデータ生成を共通化 - 意味のあるデータを使う(マジックナンバーを避ける) データ生成の階層: 1. テスト内で直接定義(最もシンプル) 2. ヘルパー関数/ファクトリ(再利用する場合) 3. Fixture(セットアップが複雑な場合)
アンチパターン
| パターン | 問題 | 解決 |
|---|---|---|
| テストの相互依存 | 実行順序で結果が変わる | 各テストで状態をリセット |
| 過度なモック | 実装詳細に依存しすぎ | 境界でのみモック |
| 巨大なテスト | 1テストで多くを検証 | 1テスト1責務 |
| マジックナンバー | データの意図が不明 | 意味のある変数名 |
| テストの重複 | 同じことを複数箇所で検証 | 責務を明確に分離 |
| 脆いアサーション | 変更に弱い完全一致 | 本質的な部分のみ検証 |
| 条件分岐のあるテスト | テスト内に if 文 | ケースごとにテスト分離 |
| 非決定的テスト | 時刻・乱数で結果が変わる | 依存を注入可能に |
| コメントアウトされたテスト | 無効化されたテスト放置 | 削除するか修正 |
| アサーションなし | 何も検証していない | 必ず expect を含める |
詳細: references/anti-patterns.md
実装ワークフロー
新機能のテスト
code
1. 要件からテストケースを洗い出す 2. 優先度でソート(ビジネスクリティカル → エッジケース) 3. ユニットテストから書く 4. 必要に応じて統合テスト追加 5. クリティカルパスは E2E テスト
バグ修正のテスト(TDD アプローチ)
code
1. バグを再現するテストを先に書く(Red) 2. 修正を実装(Green) 3. リファクタリング 4. 回帰テストとして維持
レガシーコードのテスト
code
1. 変更箇所を特定 2. その箇所の現状動作をテスト化(Characterization Test) 3. テストが通ることを確認 4. 変更を加える 5. テストが通ることを再確認
テストカバレッジの考え方
code
原則: - カバレッジは「テストの質」の指標ではない - カバレッジが低い箇所を発見するための参考値 - 100% を目標にしない(投資対効果が低い) 推奨: - ビジネスロジック: 80%+ を目安 - ユーティリティ: 90%+ を目安 - UI コンポーネント: 重要な操作フローをカバー - 設定・ボイラープレート: カバレッジ対象外でも可 注意: - カバレッジ閾値を CI に組み込む場合は段階的に引き上げる - ブランチカバレッジ > ラインカバレッジ(条件分岐の網羅度)
レビューチェックリスト
- • テストレベルが適切に選択されている
- • ビジネスクリティカルなロジックがテストされている
- • エッジケース(境界値、null、空)がテストされている
- • テストが相互に独立している
- • テストダブルが境界でのみ使われている
- • テスト名から何をテストしているか分かる
- • 1テスト1アサーション(または1責務)
- • マジックナンバーがない
リファレンス
- •references/test-doubles.md - テストダブル詳細ガイド
- •references/anti-patterns.md - テストアンチパターン詳細