# ユニットテスト知識

## テストダブルの使い分け

テストダブルは目的に応じて使い分ける。過剰なモックはテストの信頼性を下げる。

| 種類 | 目的 | 使用場面 |
|------|------|---------|
| Stub | 固定値を返す | 外部依存の出力を制御したい |
| Mock | 呼び出しを検証する | メソッド呼び出しの有無・引数を確認したい |
| Spy | 実装を残しつつ呼び出しを記録 | 副作用の検証をしたい |
| Fake | 簡易的な実装 | インメモリDBなど軽量な代替が必要 |

### モック粒度の判断

- テスト対象の直接の依存のみモックする（間接依存はモックしない）
- 「モックが多すぎる」はテスト対象の設計の問題を示唆する
- 純粋関数は依存がないのでモック不要

```typescript
// 避ける例: 内部実装をモック（振る舞いではなく実装を検証している）
vi.spyOn(service, 'privateMethod')
service.execute()
expect(service.privateMethod).toHaveBeenCalled()

// 例: 外部依存をモックし、振る舞いを検証
const repository = { findById: vi.fn().mockResolvedValue(user) }
const service = new UserService(repository)
const result = await service.getUser('id')
expect(result).toEqual(user)
```

## テストダブルの契約一致

builder、runner、adapter、provider などをテストダブルに置き換える場合、型だけでなく本番実装の意味契約を揃える。テストダブルが簡略化してよいのは、対象テストで観測しない責務に限る。

| 観点 | 確認内容 |
|------|----------|
| 戻り値 | 必須値、任意値、欠落値、部分成功の shape が本番と一致している |
| 入力伝播 | override、context、options など、本番が分岐に使う入力を受け取り検証できる |
| 制約 | 権限、能力、tool 制限、上限値などが本番と同じ意味で渡る |
| 副作用 | セッション更新、イベント発行、保存、破棄などの有無を観測できる |
| 簡略化範囲 | テストダブルで証明できない挙動を、テスト名や期待値で主張しない |

テストダブルが本番契約の一部を省略する場合、テストはその省略範囲に依存しない振る舞いだけを検証する。権限伝播、状態遷移、欠落値処理を確認するテストでは、省略されたフィールド自体がバグの温床になる。

## 境界値分析

境界値と同値分割はユニットテストの基本手法。

| 手法 | 内容 |
|------|------|
| 同値分割 | 入力を等価なグループに分け、各グループから1つずつテスト |
| 境界値分析 | 同値クラスの境界でテスト（境界、境界±1） |

```typescript
// 避ける例: 正常系のみ
test('validates age', () => {
  expect(validateAge(25)).toBe(true)
})

// 例: 境界値を含む
test('validates age at boundaries', () => {
  expect(validateAge(0)).toBe(true)    // 下限
  expect(validateAge(-1)).toBe(false)  // 下限-1
  expect(validateAge(150)).toBe(true)  // 上限
  expect(validateAge(151)).toBe(false) // 上限+1
})
```

## 振る舞い保証

ユニットテストは設定値や内部状態のスナップショットだけでなく、公開された契約が期待どおりに振る舞うことを検証する。拒否、許可、隔離、解放のような境界変更は、主要な成功/失敗ケースを deterministic に確認する。


## 自然言語・宣言的資産の検証レイヤー

プロンプトや instruction の文字列、ワークフローなどの宣言的定義は入力データである。定義の保存状態、parser・loader の構造契約、実行時の振る舞いは、それぞれ別の検証対象として扱う。

| 検証対象 | 適切な方法 |
|----------|------------|
| parser・loader の参照解決、schema、rule 解釈 | 必要最小限の専用 fixture を使った構造テスト |
| 配布される宣言的資産群 | 全件 load と schema 適合の smoke test |
| 状態遷移や副作用 | 代表的な最小シナリオを使った実行結果のテスト |
| 文字列自体が外部公開契約である値 | 完全一致テスト |
| 自然言語による分類・判断 | 代表例と反例を含むモデル評価 |
| 決定的に定義できる判定 | 自然言語からコードへ分離したユニットテスト |

```typescript
// 避ける例: 配布定義を期待値へ複製し、定義差分だけを検出する
expect(shippedWorkflow.steps.map((step) => step.name)).toEqual(['plan', 'review', 'fix'])

// 例: 最小 fixture で parser の構造契約を検証する
expect(parsedFixture.rules[0]?.next).toBe('fix')
```

個別の配布資産に含まれる step 名、rule、遷移先、設定値を期待値へ丸写しすると、実装とは独立した契約ではなく、同じ定義の複製になる。配布資産は全件 load・schema 適合で破損を検出し、遷移や副作用は最小シナリオの実行結果で検証する。

## テストフィクスチャ設計

テストデータはファクトリ関数で管理する。

- ファクトリ関数で必要最小限のフィクスチャを生成する
- テストに無関係なフィールドはデフォルト値で埋める
- 共有フィクスチャを変更して使い回さない（テスト間の独立性を保つ）

```typescript
// 避ける例: 全フィールドを毎回定義
const user = { id: '1', name: 'test', email: 'test@example.com', role: 'admin', createdAt: new Date() }

// 例: ファクトリ関数で必要最小限
const createUser = (overrides: Partial<User> = {}): User => ({
  id: 'test-id',
  name: 'test-user',
  email: 'test@example.com',
  role: 'user',
  ...overrides,
})

test('admin can delete', () => {
  const admin = createUser({ role: 'admin' })
  // テストに関係するフィールドだけ明示
})
```

## テスト対象の分離

テスト容易性は設計品質の指標。テストしにくいコードは依存が密結合している。

### 依存注入パターン

| パターン | 使用場面 |
|---------|---------|
| コンストラクタ注入 | クラスベースの依存分離 |
| 関数引数 | 関数の依存を引数で受け取る |
| モジュール差し替え | テスト時にモジュール全体を差し替える |

```typescript
// 避ける例: 直接依存を生成（テストでモック不可）
class OrderService {
  private repo = new OrderRepository()
  async create(order: Order) { return this.repo.save(order) }
}

// 例: コンストラクタ注入（テストでモック可能）
class OrderService {
  constructor(private readonly repo: OrderRepository) {}
  async create(order: Order) { return this.repo.save(order) }
}
```
