tdd について
コーディングエージェント向けのテスト駆動開発スキルです。失敗するテストを先にコミットし、最小限の実装とリファクタリングを行う手順を定めます。コミットの検査方法も示しつつ、テストが正しい理由で失敗したかは検査できないと説明します。
主な機能
- 01失敗するテストを先にコミット
- 021サイクルで1つの振る舞いを実装
- 03RED、GREEN、リファクタのコミットを分離
- 04マーカーとスクリプトによる構造検査
- 05公開インターフェース経由で動作を検証
活用シーン
- 01TDDサイクルに沿った機能開発
- 02不具合修正の回帰テスト作成
- 03RED/GREEN規約に沿ったコミットの確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ pandoscope/ skills --skill tddインストール後、Claude Code で/tddを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
deep-modules.mdinterface-design.mdlint-red.shmocking.mdrefactoring.mdtesting-anti-patterns.mdtests.md詳細
テスト駆動開発
まずテストを書きます。失敗する状態でコミットします。テストを通すための最小限のコードを書きます。成功する状態でコミットします。リファクタリングします。
基本原則: テストは実装ではなく、公開インターフェースを通じて振る舞いを検証します。コードは全面的に変更できますが、テストは変えるべきではありません。内部関数の名前を変更しただけで(振る舞いに変化がないのに)失敗するテストは、実装をテストしていました。削除してください。
失敗を確認していなければ、そのテストが正しいものをテストしている証拠はありません。コミット履歴が証拠となり、フックがその構造をチェックします。
例は tests.md、モックのガイドラインは mocking.md を参照してください。
絶対のルール
失敗するテストを先にコミットせずに、本番コードを書いてはいけません。
テストより先にコードを書いてしまいましたか? そのコードを削除してください。テストをもとに新たに実装します。削除とは削除することです。例外(使い捨てのプロトタイプ、生成コード、設定)には人間の承認が必要です。「今回だけ TDD を省略しよう」と考えていませんか? それは正当化にすぎません。
1. 計画(コードを書く前に)
プロジェクトのドメイン用語集を使い、テスト名とインターフェースの語彙をコードベースに合わせます。変更する領域の ADR を尊重してください。
- 実装手順ではなく、テストする 振る舞い を列挙します。重要な経路と複雑なロジックを優先してください。すべてをテストすることはできません。
- テスト容易性を考慮してインターフェースを設計し、ディープモジュール(小さなインターフェースと深い実装)の機会を見極めます。
- 公開インターフェースと優先する振る舞いをユーザーと確認してから、作業を進めます。
2. 水平ではなく、垂直スライスで
テストをすべて書いてから、実装をすべて書いてはいけません。それは水平スライスであり、ひどいテストを生みます。まとめて書かれたテストは、想像上の 振る舞いや物事の 形(シグネチャ、データ構造)をテストし、実際の変更に反応しなくなり、コードを理解する前にテストの構造に縛られてしまいます。
垂直スライスで作業します。テストを1つ書き、実装を1つ行う。このサイクルを繰り返します。それぞれのテストは、直前のサイクルで得た学びを反映します。
WRONG (horizontal): RED: t1 t2 t3 t4 GREEN: i1 i2 i3 i4
RIGHT (vertical): t1→i1 t2→i2 t3→i3 ...
最初のスライスはトレーサーバレットです。経路がエンドツーエンドで機能することを証明します。
3. コミット手順
1つの振る舞いにつき、RED コミットを1つ、GREEN コミットを1つ作成します。1つのブランチで複数のサイクルを行ってもかまいません。Go/Rust のリポジトリでは、マーカーを参照し、進行中のサイクルは1つにすることを推奨します。
RED を含め、すべてのコミットでツリーが lint-clean であること。 各コミットの後に、リポジトリ全体を対象として、フック一式を実行します(例:prek run --all-files)。終了コードが 0 で、ファイルが変更されていないことを確認してください。自動修正によってファイルが変更された場合は失敗です。次のサイクルを始める前に修正を適用し、問題を持ち込んだコミットに amend してください。PR の先頭だけを評価する CI であっても、中間コミットが不適切な状態であることの言い訳にはなりません。コミットごとの衛生状態は、ここで、コミット時に確認するか、まったく確認しないかのどちらかです。
STUB コミット(RED テストに不足している API サーフェスが必要な場合のみ)
RED コミットで失敗してよいのは テストのみ です。lint、フォーマット、型チェックは通る必要があります。新しいテストがまだ存在しない関数、メソッド、またはパラメーターを呼び出す場合、型チェックに失敗します。そのため、まずシグネチャだけの最小限の stub を独立したコミットとして作成し、chore(stub): を先頭に付けます。シグネチャと docstring のみで、振る舞いはありません。新しい呼び出し可能要素は NotImplementedError を送出し、新しいパラメーターは受け取って無視します。これは絶対のルールで認められる唯一の例外です。stub が定義するのは インターフェース であり、実装 を含めてはいけません。stub コミットにロジックが含まれていれば、失敗するテストなしの本番コードです。サーフェスがすでに存在する場合は、この手順をまるごと省略してください。
RED コミット
- 失敗するテストを1つ書きます。1つの振る舞いに絞り、明確な名前を付け、実際のコードを使います。避けられない場合を除き、モックは使いません。
- マーカーなしで実行し、正しい理由で失敗することを確認します(機能がないため、振る舞いが誤っているため、アサーションが失敗するため、またはスタブの
NotImplementedErrorのため)。タイプミス、インポートエラー、シンボル不足は理由として認められません。すぐに成功しますか? 既存の振る舞いをテストしています。テストを修正してください。 - マーカー(以下を参照)を追加し、テストスイートを成功させてコミットします。テストのみを変更し、接頭辞は
test(red):とします。
| 言語 | マーカー | 厳密性 |
|---|---|---|
| Python (pytest) | @pytest.mark.xfail(strict=True) | あり — XPASS はテストスイートを失敗させます |
| TS/JS (vitest) | test.fails(...) / it.fails(...) | あり |
| TS/JS (jest) | it.failing(...) | あり |
| Go | //go:build red + TestRed の接頭辞 + red-tests ジョブ | 集約のみ |
| Rust | #[ignore = "red"] + red_ の接頭辞 + red-tests ジョブ | 集約のみ |
| その他 | 厳密な想定失敗の仕組みを探します。存在しない場合は、マーカーなしでコミットし、リポジトリに RED の強制機能がないことを人間に伝えます | 該当なし |
Go/Rust のビルドタグ/無視マーカーは、通常のテストスイートからテストを除外するため、red-tests CI ジョブではマークされたテストのみを実行し、失敗を想定する必要があります(該当するテストがない場合は何もしません)。終了コードは集約結果です。RED テストが2つ進行中で、そのうち1つが誤って成功しても、ジョブ自体は成功します。厳密なマーカーならテストごとに検出できますが、ジョブでは検出できません。
GREEN コミット
- テストを通すための最もシンプルなコードを書きます。YAGNI — 将来を見越した汎用化はしません。
- 今回のサイクルで追加した RED マーカーを削除します。このコミットでは、それ以外のテスト変更はしません。
- テストスイート全体を成功させ、出力に問題がないことを確認します。失敗しますか? テストではなくコードを修正してください。接頭辞は
feat:/fix:とします。
REFACTOR(GREEN の後のみ)
重複を取り除き、名前を改善し、ヘルパーを抽出し、モジュールをより充実させます。テストは成功したまま、新しい振る舞いを追加せず、別のコミットにします。RED の間は絶対にリファクタリングしないでください。 その後、次のサイクルを始めます。
すべてのテストが成功したら、リファクタリング候補を確認します。
強制の仕組み — とその限界
lint-red.sh(スキルに含まれています。prek と CI に組み込んでください):staged/commit モードでは、test(red): コミットがテストファイルのみを変更し、マーカーを追加していること、その他のコミットではマーカーを追加していないことを確認します。merge モードでは、ツリー内にマーカーが1つでもあれば拒否します。Go/Rust のマーカーがある場合は、red-tests ジョブが存在することも確認します。
フックが確認するのは、コミットの構成とマーカーの適切な扱いです。マーカーなしのテストを実行し、正しい理由で失敗するのを確認したかどうかは検証しません。そこは自分で責任を持つ必要があります。コミットごとの Lint も同様です。インストール済みの pre-commit フックはコミット時に汚れたコミットを阻止しますが、フックがインストールされていない環境(新しいリモート環境など)では、コミット後に毎回実行する --all-files は自分で徹底しなければなりません。それでも必ず実行してください。厳密なマーカーは一部を補えます(XPASS によって、すでに存在する振る舞いをテストしていることを検出できます)。「Lint が通った」≠「TDD が検証された」です。
良いテスト
| 品質 | ルール |
|---|---|
| 振る舞いを確認する | パブリック API を通る実際の経路をテストし、リファクタリング後も有効であること |
| 最小限 | 1つのことに絞ります。名前に「そして」が含まれていますか? 分割してください。 |
| 明確 | test1 ではなく、振る舞いを名前で表します |
| 誠実 | モックではなく、コードをテストします |
// Good — tests real behavior (vitest; jest: it.failing)
test.fails('retries failed operations 3 times', async () => {
let attempts = 0;
const op = () => { attempts++; if (attempts < 3) throw new Error('fail'); return 'ok'; };
expect(await retryOperation(op)).toBe('ok');
expect(attempts).toBe(3);
});
危険信号 — STOP、削除してやり直す
実装前なのにマーカーなしのテストが成功する · なぜテストが失敗したのか説明できない · GREEN で通すためにアサーションを弱める · モックをテストする · 「今回だけ」 · 「現実的に対応しているだけ、TDD は教条的だ」
よくある落とし穴を避けるには、testing-anti-patterns.md を読んでください。
合理化一覧
| 言い訳 | 実際のところ |
|---|---|
| 「テストするほどでもないほど簡単」 | 簡単なコードでも壊れます。テストにかかる時間は30秒です。 |
| 「後でテストする」 | 後から書くテストは実装に引きずられ、「これは何をするか?」を問うのであって、「これは何をすべきか?」を問いません。 |
| 「何時間も書いたコードを削除するのはもったいない」 | サンクコストです。検証されていないコードは負債です。 |
| 「テストを書くのが難しい」 | テストしにくいものは、使いにくいものです。そのことを受け止め、インターフェースをシンプルにしてください。 |
| 「すべてモックしなければならない」 | 結合しすぎています。依存関係を注入してください。 |
| 「Lint が通ったから TDD は完了」 | フックが確認するのは構成であり、失敗を自分で確認したかどうかではありません。 |
行き詰まったとき/デバッグ
テスト方法がわからない場合は、まず望ましい API とアサーションを書きます。バグを見つけた場合は、それを再現する失敗するテストを書いてから、プロトコル全体を実行します。テストなしでバグを修正してはいけません。