テストポリシー
全ての振る舞いの変更には対応するテストが必要であり、全てのバグ修正にはリグレッションテストが必要。
原則
| 原則 |
基準 |
| Given-When-Then |
テストは3段階で構造化する |
| 1テスト1概念 |
複数の関心事を1テストに混ぜない |
| 振る舞いを検証 |
実装の詳細ではなく振る舞いをテストする |
| 独立性 |
他のテストや実行順序に依存しない |
| 再現性 |
時間やランダム性に依存せず、毎回同じ結果 |
カバレッジ基準
| 対象 |
基準 |
| 新しい振る舞い |
テスト必須。テストがなければ REJECT |
| バグ修正 |
リグレッションテスト必須。テストがなければ REJECT |
| 振る舞いの変更 |
テストの更新必須。更新がなければ REJECT |
| ビルド(型チェック) |
ビルド成功必須。失敗は REJECT |
| エッジケース・境界値 |
テスト推奨(Warning) |
テスト優先度
| 優先度 |
対象 |
| 高 |
ビジネスロジック、状態遷移 |
| 中 |
エッジケース、エラーハンドリング |
| 低 |
単純なCRUD、UIの見た目 |
テスト構造: Given-When-Then
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 と同義)
- マジックナンバー・マジックストリングを避ける
テスト戦略
- ロジックにはユニットテスト、境界にはインテグレーションテストを優先
- ユニットテストでカバーできるものにE2Eテストを使いすぎない
- 新しいロジックにE2Eテストしかない場合、ユニットテストの追加を提案する
インテグレーションテストが必須な場面
ユニットテストだけでは検証できないデータフローの結合を検証する。
| 条件 |
判定 |
| 3つ以上のモジュールを横断するデータフロー |
インテグレーションテスト必須 |
| 新しいステータス/状態が既存のワークフローに合流する |
遷移フロー全体のインテグレーションテスト必須 |
| 新しいオプションが呼び出しチェーンを通じて末端まで伝搬する |
チェーン全体の結合テスト必須 |
| 各モジュールのユニットテストが全てパスしている |
ユニットテストのみで十分(上記に該当しない場合) |
テスト環境の分離
テストインフラの設定はテストシナリオのパラメータに連動させる。ハードコードされた前提は別シナリオで壊れる。
| 原則 |
基準 |
| パラメータ連動 |
テストの入力パラメータに応じてフィクスチャ・設定を生成する |
| 暗黙の前提排除 |
特定の環境(ユーザーの個人設定等)に依存しない |
| 整合性 |
テスト設定内の関連する値は互いに矛盾しない |
| プロセス終了保証 |
テストランナーにタイムアウトと強制終了を設定し、プロセスリークを防ぐ |
// ❌ ハードコードされた前提 — 別のバックエンドでテストすると不整合になる
writeConfig({ backend: 'postgres', connectionPool: 10 })
// ✅ パラメータに連動
const backend = process.env.TEST_BACKEND ?? 'postgres'
writeConfig({ backend, connectionPool: backend === 'sqlite' ? 1 : 10 })