mcpField
D
drill-tdd

drill-tdd

github.com/kucherenkoテスト・品質8544 回インストール1 日前に更新
リポジトリ

drill-tdd について

drill-tddは、機能開発、バグ修正、リファクタリング向けのテスト駆動開発スキルです。失敗するテストを確認してから最小限の本番コードを書き、テスト合格後にリファクタリングします。

主な機能

  1. 01失敗するテストを先に作成
  2. 02失敗理由が想定どおりか確認
  3. 03合格に必要な最小限の実装
  4. 04リファクタリング前のテスト実行
  5. 05テストによるバグの再現

活用シーン

  1. 01テストファーストでの新機能開発
  2. 02回帰を防ぐバグ修正
  3. 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の遵守

  • 真実のルール:テスト出力は実際のコマンド出力であり、単なる主張ではない
  • 仕様は絶対:すべての実装は、要件に対応するテストにたどり着ける

コメント