テスト駆動開発
基本原則
振る舞いの変更が信頼を得るのは、テストが正しい理由で最初に失敗し、その後実装によってパスした場合のみです。
テストは実装の詳細ではなく、公開インターフェースを通じて振る舞いを検証すべきです。コードは完全に変わることがありますが、テストは変わるべきではありません。
優れたテストは統合テスト的です。つまり、公開APIを通じて実際のコードパスを実行します。それらはシステムが何を行うかを記述し、どのように行うかは記述しません。優れたテストは仕様のように読めます。「ユーザーは有効なカートでチェックアウトできる」は、どのような機能が存在するかを正確に示します。これらのテストは、内部構造を気にしないため、リファクタリング後も生き残ります。
悪いテストは実装に結合しています。内部の協力者をモックし、プライベートメソッドをテストし、呼び出し回数をアサートし、公開インターフェースの代わりに外部手段で検証します。危険信号:振る舞いが同じままでも、リファクタリング後にテストが壊れる。
tests.md、mocking.md、およびtesting-anti-patterns.mdを参照してください。
絶対ルール
所有するプロダクションの振る舞い変更は、失敗するテストなしには行ってはなりません。
意味:
- 一つの振る舞いに対して一つのテストを書く。
- 実行して失敗を確認する。
- 失敗が欠落している振る舞いを証明しており、タイプミスやセットアップのバグではないことを確認する。
- テストをパスさせる最小のプロダクション変更を書く。
- グリーンになった後にのみリファクタリングする。
テストよりも先に実装コードが存在する場合:
- ユーザーの作業を許可なく削除しない。
- それをスパイク/参照として扱い、検証された実装としては扱わない。
- それを脇に置き、失敗するテストを書き、テストから実装することを優先する。
- 既存のコードを保持する必要がある場合は、まず回帰テスト/特性テストを追加し、その後新しい失敗テストで振る舞いを変更する。
アンチパターン:水平スライス
すべてのテストを最初に書き、その後すべての実装を書いてはいけません。それは水平スライスです。
間違い:
RED: test1, test2, test3, test4
GREEN: impl1, impl2, impl3, impl4
正しい:
RED -> GREEN -> REFACTOR: 振る舞い 1
RED -> GREEN -> REFACTOR: 振る舞い 2
RED -> GREEN -> REFACTOR: 振る舞い 3
理由:一括テストは想像上の設計をエンコードし、フィードバックを先取りし、多くの場合、振る舞いの代わりに形状をテストします。
ワークフロー
1. 振る舞いを計画する
コードを書く前に:
- 明白でない場合、ユーザーと公開インターフェースの変更を確認する。
- 実装手順ではなく、テストすべき振る舞いをリストアップする。
- 重要なパスと複雑なロジックに優先順位を付け、偽の完全性を追いかけない。
- 深いモジュールの機会を特定する。
- テスト容易性のためにインターフェースを設計する。
- 最初のトレーサー弾の振る舞いを選ぶ。
「どの公開振る舞いが存在すべきか? どの振る舞いが最初に重要か?」と問いかける。
2. RED:一つの失敗するテスト
一つの観測可能な振る舞いに対して、一つの最小限のテストを書く。
要件:
- 公開インターフェースのみを使用する。
- 名前は振る舞いを記述する。
- 実用的な範囲で実際のコードをテストする。
- システム境界または避けられないシームでのみモックする。
- 実装前に失敗する。
最も狭い有用なコマンドを実行する。例:
npm test path/to/test.test.ts
失敗を検証する:
- テストは失敗する。セットアップ/タイプミスによるエラーではない。
- 失敗理由は欠落している振る舞いと一致する。
- すぐにパスした場合、テストが間違っているか、振る舞いが既に存在する。テストを修正するか、次の欠落している振る舞いを選ぶ。
3. GREEN:最小限の実装
現在のテストをパスするのに十分なコードだけを書く。
してはいけないこと:
- 推測の機能を追加する。
- 関係のないコードをリファクタリングする。
- 将来のオプションを追加する。
- 「ついでに簡単だから」とスコープを広げる。
実行:
npm test path/to/test.test.ts
次に関連する周辺のテストを実行する。出力はクリーンであるべき:新しい警告、フレーキーなテスト、隠れたエラーがないこと。
4. REFACTOR:グリーンの間にクリーンにする
テストがパスした後にのみリファクタリングする。有用なリファクタリングがない場合は、簡潔にそう述べて続行する。
リファクタリング候補を探す:
- 重複
- 不適切な名前
- 浅いモジュール
- 混乱する境界
- 小さなインターフェースの背後に移動できる複雑さ
ルール:
- 振る舞いと期待は変わらない。
- 各リファクタリングステップの後もテストはグリーンのまま。
- テストのリファクタリングでは、名前変更、重複排除、フィクスチャ/ビルダーの抽出、または失敗メッセージの改善を行うことがある。
- アサーションが期待される振る舞いを変更した場合、停止する:それは新しいREDサイクルである。
5. 垂直に繰り返す
前のサイクルから学んだことに基づいて、次の振る舞いを選ぶ。一度に一つの振る舞い。
サイクルごとのチェックリスト
[ ] テストは実装ではなく観測可能な振る舞いを記述している
[ ] テストは公開インターフェースのみを使用している
[ ] テストは実装前に失敗した
[ ] 失敗理由は正しかった
[ ] コードはこのテストに対して最小限である
[ ] 推測の振る舞いは追加されていない
[ ] 関連するテストがクリーンにパスする
[ ] リファクタリングはグリーンの間のみ考慮された
コミットゲート
監査可能な作業のために、実用的な場合、フェーズを個別にコミットする:
- REDコミット:テストのみ、または最小限のスキャフォールドと失敗アサーション。本文には失敗したコマンドと理由を記録する。
- GREENコミット:最小限の実装。本文にはターゲットテストがパスしたこととリファクタリングを考慮したことを記録する。
- REFACTORコミット:振る舞いや期待の変更なし。本文には何が変わったか、何が変わらなかったかを記述する。
コミットにAIの帰属を含めない。
CLI契約テスト
CLIツールの場合、コマンドサーフェスがプロダクト契約です。最も低い信頼できるレベルでの外部結合テストを優先する:
- CLI契約テスト:偽のプロバイダーまたはフィクスチャでルート/サブコマンドを実行し、stdout、stderr、終了動作、JSON形状をアサートする。
- ユニットテスト:パース、比較、分類、コンテキスト選択、エラー分類。
- プロバイダー統合テスト:ファイルシステムフィクスチャと偽の外部サービス。ネットワークや認証は不要。
- ライブスモークテスト:オプトインのみ。形状/不変条件をアサートし、揮発性の正確な出力はアサートしない。
手動QA回帰ルール
手動QAは発見手段であり、検証の基盤ではない。振る舞いのギャップを発見した場合:
- それを再現する最下層のRED回帰テストを書く。
- 失敗の証拠を検証する。
- GREENを実装する。
- 自動テストを再実行する。
- テストがパスした後にのみ手動QAを再実行する。
出力/コピー/CLI形状が変更された場合、CLI契約テストを追加または更新する。
合理化の罠
| 主張 | 応答 |
|---|---|
| 「テストするには単純すぎる」 | 単純な振る舞いでも回帰する。小さなテストを追加しよう。 |
| 「後でテストする」 | 後でパスするテストは、テストが失敗できることを証明しない。 |
| 「手動で確認した」 | 手動確認は反復可能な回帰テストではない。 |
| 「まず探索が必要」 | 結構。スパイクして、脇に置いてTDDを始めよう。 |
| 「テストが難しい」 | インターフェースが使いにくい可能性がある。シーム/設計を簡略化しよう。 |
| 「モックにすれば簡単」 | モックの振る舞いをテストしている可能性がある。アンチパターンを読もう。 |
| 「一つの大きなテストでカバーできる」 | エンドツーエンドの契約がもっと必要でない限り、振る舞いごとに一つのテストを優先しよう。 |
| 「リファクタリングに失敗テストが必要」 | 純粋なリファクタリングはグリーンで始まりグリーンのまま。振る舞いの変更にはREDが必要。 |
赤信号:停止して修正
- 失敗テストの前にプロダクションの振る舞いが変更された。
- テストはすぐにパスするが、REDと主張されている。
- 失敗理由が検査されていない。
- テスト名が実装を記述している。
- テストがプライベート呼び出し、呼び出し順序、または内部の協力者をアサートしている。
- モックのセットアップがテストロジックを支配している。
- テストのためだけに新しいメソッドが存在する。
- 実装が同意しないため、GREEN中にテストを修正している。
- 実装の前に振る舞いの水平バッチテスト。
行き詰まったとき
| 問題 | 行動 |
|---|---|
| テスト方法がわからない | 希望する公開APIと最初のアサーションを書く。 |
| テストセットアップが巨大 | フィクスチャ/ヘルパーを抽出する。それでも巨大なら、設計を簡略化する。 |
| すべてをモックしなければならない | コードがおそらく結合しすぎている。依存性注入/シームを導入する。 |
| 既存コードにテストがない | 現在の振る舞いの特性テストを追加し、次に変更用のREDを追加する。 |
| バグが不明瞭 | まず手動/ログで再現し、次に最小の失敗回帰をエンコードする。 |
完了前の確認
- 新規/変更された振る舞いにテストがある。
- 各新しい振る舞いテストが正しい理由で失敗するのを観測した。
- すべての関連テストがパスする。
- テストは公開振る舞いを検証する。
- モックは境界のみまたは正当化されている。
- テスト専用のプロダクションAPIは追加されていない。
- 手動QAで発見されたものが回帰テストになった。