test-driven-development について
test-driven-development は、失敗するテストを先に書き、最小限のコードで通し、その後リファクタリングするという流れをエージェントに案内するスキルです。新機能、バグ修正、リファクタリング、動作変更に適用され、検証チェックリストを含みます。
主な機能
- 01レッド・グリーン・リファクタリングの流れ
- 02テストが失敗する様子の事前確認
- 03良いテストの判断基準
- 04バグ修正の例
- 05完了前の検証チェックリスト
活用シーン
- 01機能実装の前に失敗するテストを書かせる
- 02バグ修正時に回帰テストを追加する
- 03リファクタリング中もテストが通ることを確認する
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ obra/ superpowers --skill test-driven-developmentインストール後、Claude Code で/test-driven-developmentを入力して読み込まれたか確認します。
同梱ファイルwriting-good-tests.md
詳細
テスト駆動開発(TDD)
概要
まずテストを書きます。失敗することを確認します。テストを通すための最小限のコードを書きます。
基本原則: テストが失敗するところを確認していなければ、そのテストが正しいものをテストしているかどうかは分かりません。
ルールの文言に違反することは、ルールの精神に違反することです。
使用するタイミング
常に:
- 新機能
- バグ修正
- リファクタリング
- 動作の変更
例外(人間のパートナーに相談してください):
- 使い捨てのプロトタイプ
- 生成コード
- 設定ファイル
「今回だけTDDを省略しよう」と考えていますか? やめてください。それは自己正当化です。
鉄の掟
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
テストより先にコードを書きましたか? 削除してください。最初からやり直します。
例外はありません:
- 「参考用」として残さないでください
- テストを書きながら「調整」しないでください
- 見ないでください
- 削除とは、削除することです
テストをもとに、改めて実装してください。それだけです。
レッド・グリーン・リファクタリング
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;
}
レッド - 失敗するテストを書く
期待する動作を示す、最小限のテストを1つ書きます。
```typescript test('retries failed operations 3 times', async () => { let attempts = 0; const operation = () => { 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つの動作
- 明確な名前
- 実際のコード(避けられない場合を除き、モックは使わない)
レッドを確認 - 失敗するところを見る
必須です。絶対に省略しないでください。
npm test path/to/test.test.ts
次の点を確認します:
- テストが失敗する(エラーではない)
- 失敗メッセージが想定どおりである
- 機能が未実装であるために失敗する(誤字が原因ではない)
テストが通りますか? 既存の動作をテストしています。テストを修正してください。
テストでエラーが発生しますか? エラーを修正し、正しく失敗するまで再実行してください。
グリーン - 最小限のコード
テストを通すための最も単純なコードを書きます。
```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 } ``` 過剰に作り込まれています機能を追加したり、ほかのコードをリファクタリングしたり、テストの範囲を超えて「改善」したりしないでください。
グリーンを確認 - 通るところを見る
必須です。
npm test path/to/test.test.ts
次の点を確認します:
- テストが通る
- ほかのテストも引き続き通る
- 出力に問題がない(エラーや警告がない)
テストが失敗しますか? テストではなく、コードを修正してください。
ほかのテストが失敗しますか? 今すぐ修正してください。
「ほかのテスト」とは、プロジェクトのテストスイートのことであり、自分のファイルだけではありません。 自分が書いたテストが通っても、テストスイート全体が通ったことにはなりません。変更を完了したと判断する前に、タスクでテストファイルが1つだけ指定されている場合でも、プロジェクトのテストコマンド(pytest 単独実行、npm test、cargo test など、リポジトリで使われているもの)を実行してください。タスクのスコープは成果物の範囲を定めるものであり、検証の範囲を定めるものではありません。その実行で見つかった失敗は、たとえ自分が引き起こしたものでなくても、名前を挙げて報告してください。スクロールしていくのを見たのに言及しなかった失敗テストがあるなら、それは省略によって報告内容を偽ったことになります。
リファクタリング - 整理する
グリーンになってからのみ行います:
- 重複を取り除く
- 名前を改善する
- ヘルパーを抽出する
テストが通る状態を保ちます。動作は追加しないでください。
繰り返す
次の機能について、次の失敗するテストを書きます。
良いテスト
| 品質 | 良い例 | 悪い例 |
|---|---|---|
| 最小限 | 1つのこと。「and」が名前に入っていますか? 分割してください。 | test('validates email and domain and whitespace') |
| 明確 | 名前が動作を説明している | test('test1') |
| 意図を示す | 期待する API を実演する | コードがどう動くべきかを分かりにくくする |
テストを書くとき、または変更するときは、テストの信頼性を保つためのルールを確認するため、writing-good-tests.md を読んでください:
- テストを書き始める前に、そのテストを失敗させる本番コードの変更を明確にする
- モックの動作ではなく、実際の動作をアサートする
- テスト専用のコードはテストユーティリティに置き、本番クラスには含めない
- モックを使う前に、依存関係が引き起こす副作用を理解する
よくある言い訳
| 言い訳 | 実際のところ |
|---|---|
| 「テストするには単純すぎる」 | 単純なコードでも壊れます。テストは30秒で終わります。 |
| 「あとでテストする」 | あとから書いたテストはすぐに通ります。つまり、何も証明できていません。間違ったものをテストしたり、動作ではなく実装をテストしたり、忘れていた境界ケースを見落としたりする可能性があります。失敗するところを見ていないため、バグを検出できることを証明できていません。テストファーストなら、必ず失敗を確認できます。 |
| 「あとからテストしても同じ目標を達成できる(形式ではなく精神が大切)」 | あとから書くテストが答えるのは「これは何をするのか?」ですが、テストファーストが答えるのは「これは何をするべきか?」です。あとから書くテストは、すでに書いたコードに影響されるため、発見できたはずのケースではなく、覚えていたケースを検証することになります。テストが機能する証拠のないカバレッジです。 |
| 「すでに手動でテストした」 | 手動テストは場当たり的です。何をカバーしたか記録がなく、コード変更後に再実行する方法もなく、プレッシャーの中ではケースを忘れがちです。「試したときは動いた」≠網羅的です。自動テストなら毎回同じように実行できます。 |
| 「X時間分を削除するのは無駄だ」 | サンクコストの誤謬です。どちらにしても、その時間はすでに使われています。本当の選択肢は、TDDで書き直す(高い信頼性)か、そのまま残してあとからテストを追加する(低い信頼性で、バグが発生しやすい)かです。信頼できないコードを残すことこそ無駄です。 |
| 「参考として残して、テストを先に書く」 | そのコードに合わせてしまいます。それはあとからテストするのと同じです。削除するなら、完全に削除してください。 |
| 「まず調査する必要がある」 | 問題ありません。調査用のコードは破棄し、TDDで始めてください。 |
| 「テストが難しい=設計が不明瞭」 | テストの声に耳を傾けてください。テストが難しいなら、使うのも難しいということです。 |
| 「TDDは時間がかかる」 | TDDこそ現実的な方法です。コミット前にバグを検出し、リグレッションを防ぎ、安心してリファクタリングできます。「現実的」な近道は、本番環境でのデバッグにつながり、速くなるどころか遅くなります。 |
| 「手動テストのほうが速い」 | 手動テストでは境界ケースを証明できません。変更するたびに再テストが必要になります。 |
| 「既存コードにテストがない」 | いま改善しているのです。既存コードにもテストを追加してください。 |
危険信号 — 停止してやり直す
- テストより先にコードを書く
- 実装後にテストする
- テストがすぐに通る
- テストが失敗した理由を説明できない
- テストを「あとで」追加する
- 「今回だけ」と正当化する
- 「すでに手動でテストした」
- 「あとからテストしても同じ目的を達成できる」
- 「形式ではなく精神が大切」
- 「参考として残す」または「既存コードに合わせる」
- 「X時間も費やしたのだから、削除するのは無駄だ」
- 「TDDは教条的だ。自分は現実的にやっている」
- 「今回は事情が違うので……」
これらはすべて、コードを削除してTDDでやり直すべきという意味です。
例:バグ修正
バグ: 空のメールアドレスが受け入れられる
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
リファクタリング 必要に応じて、複数のフィールドに対するバリデーションを抽出します。
検証チェックリスト
作業を完了とする前に、次を確認してください。
- 新しい関数/メソッドにはすべてテストがある
- 実装前に各テストが失敗するのを確認した
- 各テストは想定した理由で失敗した(機能不足であり、タイプミスではない)
- 各テストを通すために必要最小限のコードを書いた
- すべてのテストが通る
- 出力がクリーンである(エラーや警告がない)
- テストでは実際のコードを使用している(避けられない場合に限りモックを使用)
- 境界ケースとエラーをカバーしている
すべての項目にチェックを付けられませんか?TDDを省略しています。やり直してください。
行き詰まったとき
| 問題 | 解決策 |
|---|---|
| テスト方法がわからない | 望ましいAPIを書いてください。まずアサーションを書いてください。人間のパートナーに相談してください。 |
| テストが複雑すぎる | 設計が複雑すぎます。インターフェースをシンプルにしてください。 |
| すべてをモックする必要がある | コードの結合度が高すぎます。依存性注入を使ってください。 |
| テストのセットアップが大がかり | ヘルパーを抽出してください。それでも複雑ですか?設計をシンプルにしてください。 |
統合のデバッグ
バグを見つけましたか?そのバグを再現する失敗テストを書いてください。TDDのサイクルに従ってください。テストによって修正を証明し、リグレッションを防ぎます。
テストなしでバグを修正してはいけません。
最終ルール
Production code → test exists and failed first
Otherwise → not TDD
人間のパートナーの許可がない限り、例外は認められません。