test-driven-development について
再現可能な検証を伴う機能変更やバグ修正に取り組む開発者向けのテスト駆動開発スキルです。Red-Green-Refactor の手順を示し、テストが想定した失敗を検出することと既存の変更を保つことを重視します。
主な機能
- 01Red-Green-Refactor ワークフロー
- 02想定した理由でテストが失敗するか確認
- 03合格に必要な最小限の変更
- 04回帰テストと特性テスト
- 05テスト品質とアンチパターンの指針
- 06検証チェックリスト
活用シーン
- 01再現可能なテストで機能を変更
- 02修正前にバグを再現
- 03回帰テストの検出力を検証
- 04テスト合格後にリファクタリング
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ sickn33/ agentic-awesome-skills --skill test-driven-developmentインストール後、Claude Code で/test-driven-developmentを入力して読み込まれたか確認します。
同梱ファイルtesting-anti-patterns.md
詳細
テスト駆動開発(TDD)
概要
まずテストを書きます。それが失敗するのを確認します。合格するための最小限のコードを書きます。
核心となる原則: テストが失敗するのを確認していなければ、そのテストが正しいものをテストしているかどうかはわかりません。
ルールの文言に違反することは、ルールの精神に違反することです。
いつ使うか
再現可能なテストによって要件を実証したり、バグを再現したりできるような振る舞いの変更に使用します。まずリポジトリのテストランナーと既存のカバレッジを確認してください。コピー、生成された出力、影響の少ない設定については、単体テストをでっち上げるのではなく、適切な焦点を絞った検証を使用してください。
既存の作業を保持する
可能な場合は、修復の前に対象のリグレッションテストを書き、期待どおりの理由で失敗することを検証します。実装がすでに存在する場合は、それを保持し、特性化テスト/リグレッションテストを追加します。理想的なテストファーストの履歴を再構築するために、ユーザーの作業を削除したり、ブランチをリセットしたり、動作するコードを書き換えたりしないでください。テストが修正に先行したかどうかを正直に述べてください。
レッド・グリーン・リファクタリング
digraph tdd_cycle {
rankdir=LR;
red [label="RED\nWrite failing test", shape=box, style=filled, fillcolor="#ffcccc"];
verify_red [label="Verify fails\ncorrectly", shape=diamond];
green [label="GREEN\nMinimal code", shape=box, style=filled, fillcolor="#ccffcc"];
verify_green [label="Verify passes\nAll green", shape=diamond];
refactor [label="REFACTOR\nClean up", shape=box, style=filled, fillcolor="#ccccff"];
next [label="Next", shape=ellipse];
red -> verify_red;
verify_red -> green [label="yes"];
verify_red -> red [label="wrong\nfailure"];
green -> verify_green;
verify_green -> refactor [label="yes"];
verify_green -> green [label="no"];
refactor -> verify_green [label="stay\ngreen"];
verify_green -> next;
next -> red;
}
RED - 失敗するテストを書く
何が起こるべきかを示す最小限のテストを1つ書きます。
```typescript test('succeeds on the third attempt', async () => { let attempts = 0; const operation = async () => { attempts++; if (attempts < 3) throw new Error('fail'); return 'success'; };const result = await retryOperation(operation);
expect(result).toBe('success'); expect(attempts).toBe(3); });
明確な名前、実際の振る舞いをテスト、1つのこと
</Good>
<Bad>
```typescript
test('retry works', async () => {
const mock = jest.fn()
.mockRejectedValueOnce(new Error())
.mockRejectedValueOnce(new Error())
.mockResolvedValueOnce('success');
await retryOperation(mock);
expect(mock).toHaveBeenCalledTimes(3);
});
曖昧な名前、コードではなくモックをテスト
要件:
- 1つの振る舞い
- 明確な名前
- 実際のコード(やむを得ない場合を除きモックを使わない)
RED を検証する - 失敗を確認する
必須です。決して省略しないでください。
npm test path/to/test.test.ts
確認:
- テストが失敗する(エラーではない)
- 失敗メッセージが期待どおりである
- 機能が欠けているために失敗する(タイプミスではない)
テストが合格する? それがすでに要求された振る舞いを特性化しているかどうかを判断します。リグレッションの場合は、以前のリビジョンまたは分離された制御された変更を用いて、そのテストが欠陥を検出することを証明します。red を強制するためだけに正しいアサーションを変更しないでください。
テストがエラーになる? エラーを修正し、正しく失敗するまで再実行します。
GREEN - 最小限のコード
テストに合格するための最も単純なコードを書きます。
```typescript async function retryOperation(fn: () => Promise): Promise { for (let i = 0; i < 3; i++) { try { return await fn(); } catch (e) { if (i === 2) throw e; } } throw new Error('unreachable'); } ``` 合格するのにちょうど十分 ```typescript async function retryOperation( fn: () => Promise, options?: { maxRetries?: number; backoff?: 'linear' | 'exponential'; onRetry?: (attempt: number) => void; } ): Promise { // YAGNI } ``` 過剰設計機能を追加したり、他のコードをリファクタリングしたり、テストを超えて「改善」したりしないでください。
GREEN を検証する - 合格を確認する
必須です。
npm test path/to/test.test.ts
確認:
- テストが合格する
- 他のテストも引き続き合格する
- 出力がクリーンである(エラー、警告がない)
テストが失敗する? テストではなくコードを修正します。
他のテストが失敗する? 今すぐ修正します。
REFACTOR - クリーンアップ
green の後だけ:
- 重複を削除する
- 名前を改善する
- ヘルパーを抽出する
テストを green に保ちます。振る舞いを追加しないでください。
繰り返す
次の機能のための次の失敗するテストへ。
良いテスト
| 品質 | 良い | 悪い |
|---|---|---|
| 最小限 | 1つのこと。名前に「and」がある? 分割する。 | test('validates email and domain and whitespace') |
| 明確 | 名前が振る舞いを説明する | test('test1') |
| 意図を示す | 望ましい API を実証する | コードが何をすべきかを不明瞭にする |
なぜ順序が重要か
失敗するテストは、実装前に誤解された要件を明らかにすることができます。修正後に書かれたテストも依然として価値があるかもしれませんが、元の欠陥に対するその感度には証拠が必要です。タイミングもカバレッジ率も、アサーションが意味を持つことを証明しません。
失敗がインポートの欠落、利用できないサービス、または不正なフィクスチャによって引き起こされている場合は、結果を解釈する前にそのセットアップを修復してください。実用的な場合は実際の境界を使用してください。モックは、テスト対象のコントラクトを維持しつつ外部依存関係を分離する場合に役立ちます。
例: バグ修正
バグ: 空のメールアドレスが受け入れられる
RED
test('rejects empty email', async () => {
const result = await submitForm({ email: '' });
expect(result.error).toBe('Email required');
});
RED を検証する
$ npm test
FAIL: expected 'Email required', got undefined
GREEN
function submitForm(data: FormData) {
if (!data.email?.trim()) {
return { error: 'Email required' };
}
// ...
}
GREEN を検証する
$ npm test
PASS
REFACTOR 必要に応じて複数のフィールドのバリデーションを抽出します。
検証チェックリスト
作業を完了とする前に:
- 変更された動作と結果につながる障害経路に適切なテストがある
- 回帰への感度が実証されている。テストのタイミングは正直に報告されている
- 各テストが期待どおりの理由で失敗した (機能の欠落であり、タイプミスではない)
- 各テストを通過させるための最小限のコードを書いた
- すべてのテストが通過する
- 出力がクリーン (エラー、警告なし)
- テストは実際のコードを使用している (モックは避けられない場合のみ)
- エッジケースとエラーが網羅されている
満たせなかったチェック項目とその結果を記録してください。チェックリストを完了させるために作業を消したり、観測していない失敗を主張したりしないでください。
行き詰まったとき
| 問題 | 解決策 |
|---|---|
| テストの書き方がわからない | 望ましい API を書く。まずアサーションを書く。人間のパートナーに尋ねる。 |
| テストが複雑すぎる | 設計が複雑すぎる。インターフェースを簡素化する。 |
| すべてをモックしなければならない | コードの結合度が高すぎる。依存性注入を使用する。 |
| テストのセットアップが膨大 | ヘルパーを抽出する。それでも複雑か? 設計を簡素化する。 |
デバッグの統合
バグを見つけましたか? それを再現する失敗するテストを書いてください。TDD サイクルに従ってください。テストは修正を証明し、回帰を防ぎます。
バグ修正には再現可能な回帰を優先してください。テストが障害を合理的に再現できない場合は、別の明示的な検証手段を使用してください。
テストのアンチパターン
モックやテストユーティリティを追加するときは、よくある落とし穴を避けるために @testing-anti-patterns.md を読んでください:
- 実際の動作ではなくモックの動作をテストする
- 本番クラスにテスト専用メソッドを追加する
- 依存関係を理解せずにモックする
入力と期待される結果
ユーザーに見える要件、現在の実装、既知のランナー、および制御されたフィクスチャが必要です。空のメールの例では、失敗は「バリデーションの欠落」でなければならず、ネットワーク障害ではありません。期待: 回帰は欠陥のある動作で失敗し、最小限の修正後に通過し、既存の有効な送信は引き続き機能します。
制限事項
- 通過する単体テストは、ブラウザ、パッケージ化されたランタイム、またはプロバイダー統合の動作を証明しません。
- 再試行の例は再試行安全な操作を前提としています。本番の再試行には、明示的な冪等性、キャンセル、および再試行可能なエラーポリシーが必要です。
- テストファーストの順序は、誤った要件や過剰なモックを防ぎません。アサーションと実際の境界を検査してください。
- 無関係な変更を保持し、すべての
npm testが同じ引数を受け入れると仮定するのではなく、プロジェクトの既存のテストコマンドを使用してください。