drill-tdd について
drill-tddは、機能開発、バグ修正、リファクタリング向けのテスト駆動開発スキルです。失敗するテストを確認してから最小限の本番コードを書き、テスト合格後にリファクタリングします。
主な機能
- 01失敗するテストを先に作成
- 02失敗理由が想定どおりか確認
- 03合格に必要な最小限の実装
- 04リファクタリング前のテスト実行
- 05テストによるバグの再現
活用シーン
- 01テストファーストでの新機能開発
- 02回帰を防ぐバグ修正
- 03テストを維持しながらのリファクタリング
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ kucherenko/ gangsta --skill drill-tddインストール後、Claude Code で/drill-tddを入力して読み込まれたか確認します。
詳細
ドリル:テスト駆動開発
概要
まずテストを書きます。失敗することを確認します。合格するための最小限のコードを書きます。
基本原則: テストが失敗するのを確認していないなら、そのテストが正しいことを検証しているかどうかは分かりません。
ルールの文面に違反することは、ルールの精神に違反することです。
使うタイミング
常に:
- 新機能
- バグ修正
- リファクタリング
- 動作の変更
例外(Don に確認してください):
- 使い捨てのプロトタイプ
- 生成されたコード
- 設定ファイル
「今回だけドリルを省略しよう」と考えていますか? やめてください。それは自分を正当化しているだけです。
鉄の掟(テストのオメルタ)
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;
}
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つのテストにつき1つの動作
- 明確で説明的な名前
- 実際のコード(避けられない場合を除き、モックは使わない)
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 — over-engineered } ``` 過剰に設計されている機能を追加したり、他のコードをリファクタリングしたり、テストの範囲を超えて「改善」したりしないでください。
GREEN を確認 — 合格するのを確認する
必須です。
テストを実行して、次のことを確認します:
- テストが合格する
- 他のテストも引き続き合格する
- 出力に問題がない(エラーや警告がない)
テストが失敗しましたか? テストではなくコードを修正してください。
他のテストが失敗しましたか? 今すぐ修正してください。
REFACTOR — 整理する
グリーンの後にのみ、次の作業を行います:
- 重複を取り除く
- 名前を改善する
- ヘルパーを抽出する
テストをグリーンのままに保ちます。動作を追加しないでください。
繰り返す
次の動作に対して、次の失敗するテストを書きます。
良いテスト
| 品質 | 良い例 | 悪い例 |
|---|---|---|
| 最小限 | 1つのこと。「and」が名前にあるなら分割します。 | test('validates email and domain and whitespace') |
| 明確 | 名前が動作を説明している | test('test1') |
| 意図が分かる | 望ましい API を示す | コードが何をすべきか分かりにくくする |
順序が重要な理由
「動作確認のために、後からテストを書きます」
コードの後に書いたテストは、すぐに合格します。すぐに合格しても、何も証明されません:
- 間違ったことをテストしている可能性がある
- 動作ではなく、実装をテストしている可能性がある
- 忘れていたエッジケースを見逃す可能性がある
- バグを捕捉するところを一度も見ていない
テストファーストなら、テストが失敗するのを確認でき、実際に何かをテストしていることが証明されます。
「X 時間分の作業を削除するのは無駄です」
サンクコストの誤謬です。その時間はすでに過ぎています。今できる選択肢は次のとおりです:
- 削除して TDD で書き直す(高い確信が得られる)
- 残して後からテストを追加する(確信が低く、バグが発生しやすい)
「無駄」なのは、信頼できないコードを残すことです。
「TDD は教条的です。実用的であるとは、状況に合わせることです」
TDD は実用的です:
- コミット前にバグを見つける(後からデバッグするより速い)
- リグレッションを防ぐ(テストが不具合をすぐに検出する)
- 動作を文書化する(テストでコードの使い方が分かる)
- リファクタリングを可能にする(自由に変更でき、テストが不具合を検出する)
「実用的」な近道は、本番環境でのデバッグにつながり、かえって時間がかかります。
よくある言い訳
| 言い訳 | 実際のところ |
|---|---|
| 「テストするには簡単すぎる」 | 簡単なコードでも壊れます。テストは30秒で済みます。 |
| 「あとでテストする」 | すぐにテストが通っても、何の証明にもなりません。 |
| 「すでに手動でテストした」 | 場当たり的なテストは、体系的なテストとは異なります。記録がなく、再実行もできません。 |
| 「X時間分を削除するのはもったいない」 | サンクコストの誤謬です。検証されていないコードを残すのは負債です。 |
| 「参考用に残して、先にテストを書く」 | それを流用することになります。それは後からテストするのと同じです。削除するなら完全に削除してください。 |
| 「まずは調査が必要」 | かまいません。調査用のコードは捨てて、ドリルから始めてください。 |
| 「テストが難しい=設計が不明瞭」 | テストの難しさに耳を傾けてください。テストが難しいものは、使うのも難しいものです。 |
| 「TDDでは遅くなる」 | ドリルはデバッグより速く進められます。 |
| 「既存コードにテストがない」 | 改善しようとしているのです。変更する箇所にはテストを追加してください。 |
危険信号 — いったん止めて、最初からやり直してください
- テストより先にコードを書く
- 実装後にテストする
- テストがすぐに通る
- テストが失敗した理由を説明できない
- テストを「あとで」追加する
- 「今回だけ」と正当化する
- 「参考用に残す」または「既存コードを流用する」
- 「すでにX時間かけたので、削除するのはもったいない」
- 「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' };
}
// ...existing code
}
GREENを検証
$ npm test
PASS
REFACTOR — 必要に応じて、複数のフィールドで使えるようにバリデーションを切り出します。
検証チェックリスト
作業完了とする前に、次の項目を確認してください。
- 新しく追加した関数/メソッドすべてにテストがある
- 実装前に、各テストが失敗するのを確認した
- 各テストは想定どおりの理由(機能が未実装であり、タイプミスではない)で失敗した
- 各テストを通すための最小限のコードを書いた
- すべてのテストが通る
- 出力に問題がない(エラーや警告がない)
- テストでは実際のコードを使っている(避けられない場合に限りモックを使う)
- エッジケースとエラーをカバーしている
すべての項目にチェックを付けられませんか?ドリルを省略しています。最初からやり直してください。
デバッグとの統合
バグが見つかりましたか?再現する失敗テストを書いてください。ドリルのサイクルに従ってください。テストによって修正を検証し、再発を防止できます。
テストなしでバグを修正してはいけません。まず gangsta:interrogation-debugging を使って根本原因を調査してください。
行き詰まったとき
| 問題 | 解決策 |
|---|---|
| テスト方法がわからない | 望ましいAPIを書いてください。先にアサーションを書いてください。ドンに相談してください。 |
| テストが複雑すぎる | 設計が複雑すぎます。インターフェースを簡素化してください。 |
| すべてをモックする必要がある | コードの結合度が高すぎます。依存性注入を使ってください。 |
| テストのセットアップが大きすぎる | ヘルパーを切り出してください。それでも複雑なら、設計を簡素化してください。 |
Omertaの遵守
- 真実のルール:テスト出力は実際のコマンド出力であり、単なる主張ではない
- 仕様は絶対:すべての実装は、要件に対応するテストにたどり着ける