# テストポリシー

全ての振る舞いの変更には対応するテストが必要であり、全てのバグ修正にはリグレッションテストが必要。

## 原則

| 原則 | 基準 |
|------|------|
| Given-When-Then | テストは3段階で構造化する |
| 1テスト1概念 | 複数の関心事を1テストに混ぜない |
| 振る舞いを検証 | 実装の詳細ではなく振る舞いをテストする |
| 独立性 | 他のテストや実行順序に依存しない |
| 再現性 | 時間やランダム性に依存せず、毎回同じ結果 |

## カバレッジ基準

| 対象 | 基準 |
|------|------|
| 新しい振る舞い | テスト必須。テストがなければ REJECT |
| バグ修正 | リグレッションテスト必須。テストがなければ REJECT |
| 振る舞いの変更 | テストの更新必須。更新がなければ REJECT |
| ビルド（型チェック） | ビルド成功必須。失敗は REJECT |
| エッジケース・境界値 | テスト推奨（Warning） |

## テスト優先度

| 優先度 | 対象 |
|--------|------|
| 高 | ビジネスロジック、状態遷移 |
| 中 | エッジケース、エラーハンドリング |
| 低 | 単純なCRUD |

**注意:** デザイン参照が指定されている場合、UIの見た目の検証は中優先度に格上げする。デザイン忠実再現ポリシーを参照。

## テスト構造: Given-When-Then

```typescript
test('ユーザーが存在しない場合、NotFoundエラーを返す', async () => {
  // Given: 存在しないユーザーID
  const nonExistentId = 'non-existent-id'

  // When: ユーザー取得を試みる
  const result = await getUser(nonExistentId)

  // Then: NotFoundエラーが返る
  expect(result.error).toBe('NOT_FOUND')
})
```

## テスト品質

| 観点 | 良い | 悪い |
|------|------|------|
| 独立性 | 他のテストに依存しない | 実行順序に依存 |
| 型安全 | コードはビルド（型チェック）が通ること |
| 再現性 | 毎回同じ結果 | 時間やランダム性に依存 |
| 明確性 | 失敗時に原因が分かる | 失敗しても原因不明 |
| 焦点 | 1テスト1概念 | 複数の関心事が混在 |

### 命名

テスト名は期待される振る舞いを記述する。`should {期待する振る舞い} when {条件}` パターンを使う。

### 構造

- Arrange-Act-Assert パターン（Given-When-Then と同義）
- マジックナンバー・マジックストリングを避ける

## 再取得ループのリグレッション

画面の初期取得がある場合、無関係な再レンダ、loading 切替、Context callback の参照変化で API が再実行されないことをテストで担保する。

| 基準 | 判定 |
|------|------|
| 初期取得バグ修正に対し、重複 API 呼び出しの回帰テストがない | REJECT |
| 1回呼ばれたことだけを確認し、再レンダ後の安定性を見ていない | 警告 |
| rerender や state 更新後も呼び出し回数が増えないことを検証している | OK |

## 到達経路のリグレッション

利用者向け機能や画面を追加・変更した場合、利用者がその機能へ到達できることをテストまたは同等の検証で担保する。

| 基準 | 判定 |
|------|------|
| 新規の画面・機能を追加したのに、到達経路や起動条件の検証がない | REJECT |
| 画面ファイル単体の描画だけを見て、入口からの到達確認をしていない | 警告 |
| route、メニュー、ボタン、リンク、外部呼び出しなど実際の入口から対象機能へ到達できることを確認している | OK |

## UIライブラリ統合のリグレッション

DataGrid、日付ピッカー、仮想リスト、チャートなど、外部 UI ライブラリの主要コンポーネントを導入・変更した場合は、実コンポーネントをマウントするテストでクラッシュしないことを担保する。

| 基準 | 判定 |
|------|------|
| 外部 UI ライブラリの主要コンポーネントを追加・変更したのに、実マウントの回帰テストがない | REJECT |
| ライブラリの props 整合性を、浅いモックや存在確認だけで済ませている | 警告 |
| route から対象画面を描画し、主要 UI が例外なくマウントされることを確認している | OK |
| 主要 UI コンポーネント単体でも、代表的な props で実 render している | OK |

## テスト戦略

- ロジックにはユニットテスト、境界にはインテグレーションテストを優先
- ユニットテストでカバーできるものにE2Eテストを使いすぎない
- 新しいロジックにE2Eテストしかない場合、ユニットテストの追加を提案する

### インテグレーションテストが必須な場面

ユニットテストだけでは検証できないデータフローの結合を検証する。

| 条件 | 判定 |
|------|------|
| 3つ以上のモジュールを横断するデータフロー | インテグレーションテスト必須 |
| 新しいステータス／状態が既存のワークフローに合流する | 遷移フロー全体のインテグレーションテスト必須 |
| 新しいオプションが呼び出しチェーンを通じて末端まで伝搬する | チェーン全体の結合テスト必須 |
| 各モジュールのユニットテストが全てパスしている | ユニットテストのみで十分（上記に該当しない場合） |

## ユニットテスト基準

| 基準 | 判定 |
|------|------|
| テスト対象の内部実装をモックする（振る舞いではなく実装を検証） | REJECT |
| テスト間でフィクスチャを共有して変更する | REJECT。テスト独立性の喪失 |
| モックの戻り値が実際の型と乖離している | 警告。型安全なモックを使う |
| 正常系のみテストして境界値がない | 警告 |

## E2Eテスト基準

| 基準 | 判定 |
|------|------|
| 外部API呼び出しをモックせず本番APIを叩く | REJECT。テストの再現性が失われる |
| テスト対象のコア処理をモックする | REJECT。E2Eの意味がなくなる |
| 固定 sleep でタイミングを合わせる | REJECT。状態ベースの待機を使う |
| テスト間で共有状態を持つ | 警告。テストの独立性が損なわれる |
| 正常フローだけテストして異常フローがない | 警告 |
| ユニットテストでカバーできるロジック検証をE2Eで書く | 警告 |

## テスト環境の分離

テストインフラの設定はテストシナリオのパラメータに連動させる。ハードコードされた前提は別シナリオで壊れる。

| 原則 | 基準 |
|------|------|
| パラメータ連動 | テストの入力パラメータに応じてフィクスチャ・設定を生成する |
| 暗黙の前提排除 | 特定の環境（ユーザーの個人設定等）に依存しない |
| 整合性 | テスト設定内の関連する値は互いに矛盾しない |
| プロセス終了保証 | テストランナーにタイムアウトと強制終了を設定し、プロセスリークを防ぐ |

```typescript
// ❌ ハードコードされた前提 — 別のバックエンドでテストすると不整合になる
writeConfig({ backend: 'postgres', connectionPool: 10 })

// ✅ パラメータに連動
const backend = process.env.TEST_BACKEND ?? 'postgres'
writeConfig({ backend, connectionPool: backend === 'sqlite' ? 1 : 10 })
```
