test-driven-development について
test-driven-development は、コーディングエージェントにテスト駆動開発の手順を示すスキルです。機能追加、バグ修正、リファクタリングなどで、失敗するテストを先に確認してから最小限のコードを実装します。
主な機能
- 01Red-Green-Refactor のサイクルに従う
- 02実装前に失敗するテストを作成・確認
- 03テストを通す最小限のコードを実装
- 04テスト通過後にリファクタリング
- 05チェックリストとアンチパターンを提示
活用シーン
- 01新機能をテスト先行で実装
- 02失敗するテストでバグを再現して修正
- 03リファクタリング時に動作の退行を確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ lucianghinda/ superpowers-ruby --skill test-driven-developmentインストール後、Claude Code で/test-driven-developmentを入力して読み込まれたか確認します。
同梱ファイルtesting-anti-patterns.mdtesting-strategy.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;
}
RED - 失敗するテストを書く
期待する動作を示す、最小限のテストを1つ書きます。
```ruby test "retries failed operations 3 times" do attempts = 0 operation = -> { attempts += 1 raise "fail" if attempts < 3 "success" }result = retry_operation(operation)
assert_equal "success", result assert_equal 3, attempts end
明確な名前、実際の動作をテストし、対象は1つ
</Good>
<Bad>
```ruby
test "retry works" do
call_count = 0
stub_op = -> { call_count += 1 }
retry_operation(stub_op)
assert_equal 3, call_count # tests call count, not behavior
end
曖昧な名前、結果ではなく呼び出しをテストしている
要件:
- 動作は1つ
- 明確な名前
- 実際のコード(避けられない場合を除き、モックは使わない)
REDを確認 - 失敗するのを確認する
必須です。絶対に省略しないでください。
bin/rails test test/models/retry_test.rb
確認事項:
- テストが失敗する(エラーではない)
- 失敗メッセージが期待どおりである
- 機能が未実装のために失敗している(タイプミスが原因ではない)
テストが成功する場合: 既存の動作をテストしています。テストを修正してください。
テストでエラーが発生する場合: エラーを修正し、正しく失敗するまで再実行してください。
GREEN - 最小限のコードを書く
テストを通すための最も簡単なコードを書きます。
```ruby def retry_operation(fn, max_attempts: 3) attempts = 0 begin fn.call rescue => e attempts += 1 retry if attempts < max_attempts raise end end ``` 通すために必要な分だけ ```ruby def retry_operation(fn, max_attempts: 3, backoff: :linear, on_retry: nil, jitter: false, timeout: nil) # YAGNI end ``` 過剰な設計機能を追加したり、ほかのコードをリファクタリングしたり、テストの範囲を超えて「改善」したりしないでください。
GREENを確認 - 成功するのを確認する
必須です。
bin/rails test test/models/retry_test.rb
確認事項:
- テストが成功する
- ほかのテストも引き続き成功する
- 出力がクリーンである(エラーや警告がない)
テストが失敗する場合: テストではなく、コードを修正してください。
ほかのテストが失敗する場合: すぐに修正してください。
REFACTOR - 整理する
グリーンになった後にのみ、次のことを行います:
- 重複を取り除く
- 名前を改善する
- ヘルパーを抽出する
テストを成功した状態に保ちます。振る舞いは追加しないでください。
繰り返す
次の機能に対する、次の失敗するテストを書きます。
良いテスト
| 品質 | 良い例 | 悪い例 |
|---|---|---|
| 最小限 | 対象は1つ。名前に「and」があるなら、分割してください。 | test('validates email and domain and whitespace') |
| 明確 | 名前が動作を説明している | test('test1') |
| 意図がわかる | 期待するAPIを示している | コードが何をすべきかをわかりにくくしている |
なぜ順序が重要なのか
「動作することは後でテストして確認すればいい」
コードの後に書かれたテストは、即座に通ります。即座に通ることは、何も証明しません:
- 間違ったものをテストしているかもしれない
- 振る舞いではなく、実装をテストしているかもしれない
- 忘れていたエッジケースを見逃すかもしれない
- そのテストがバグを検出するところを一度も見ていない
テストファーストでは、テストが失敗するところを必ず確認することになり、そのテストが実際に何かをテストしていることが証明されます。
「エッジケースはすべて手動でテスト済みだ」
手動テストは場当たり的です。すべてをテストしたと思っていても:
- どのようなテストを行ったかの記録がない
- コードが変更されたときに再実行できない
- プレッシャーの下では、ケースを忘れやすい
- 「試したときは動いた」は、網羅的であることを意味しない
自動テストは体系的です。毎回同じ方法で実行されます。
「X時間の作業を削除するのは無駄だ」
サンクコストの誤謬です。その時間はすでに失われています。今あなたが選べるのは:
- 削除して、TDD で書き直す(さらにX時間、信頼度は高い)
- そのまま残して、後からテストを追加する(30分、信頼度は低く、バグがある可能性が高い)
「無駄」なのは、信頼できないコードを残すことのほうです。実際のテストがない動作するコードは、技術的負債です。
「TDD は教条的であり、実用的であるとは状況に適応することだ」
TDD こそが実用的です:
- コミット前にバグを発見できる(事後のデバッグより速い)
- リグレッションを防げる(テストが即座に破損を検出する)
- 振る舞いを文書化できる(テストがコードの使い方を示す)
- リファクタリングを可能にする(自由に変更でき、テストが破損を検出する)
「実用的」な近道は、本番環境でのデバッグを意味し、結果としてより遅くなります。
「後からのテストでも同じ目的は達成できる。大切なのは形式ではなく精神だ」
いいえ。後からのテストは「これは何をするのか?」に答え、テストファーストは「これは何をすべきか?」に答えます。
後からのテストは、あなたの実装に偏っています。要求されていることではなく、作ったものをテストします。発見されたエッジケースではなく、思い出せたエッジケースを検証します。
テストファーストは、実装前にエッジケースの発見を強制します。後からのテストは、あなたがすべてを思い出せたかを検証しますが、実際には思い出せていません。
後から書いた30分のテストは、TDD ではありません。カバレッジは得られても、テストが機能することの証明は失われます。
よくある言い訳
| 言い訳 | 実際 |
|---|---|
| 「単純すぎてテストする必要がない」 | 単純なコードも壊れます。テストには30秒しかかかりません。 |
| 「後でテストする」 | 即座に通るテストは、何も証明しません。 |
| 「後からのテストでも同じ目標を達成できる」 | 後からのテスト=「これは何をするのか?」。テストファースト=「これは何をすべきか?」。 |
| 「すでに手動でテスト済み」 | 場当たり的 ≠ 体系的。記録がなく、再実行もできません。 |
| 「X時間を削除するのは無駄」 | サンクコストの誤謬です。検証されていないコードを残すことは、技術的負債です。 |
| 「参考として残し、先にテストを書く」 | 結局それを適応させることになります。それは後からのテストです。削除とは削除することです。 |
| 「まず探索する必要がある」 | 問題ありません。探索したコードは捨て、TDD で始めてください。 |
| 「テストが難しい=設計が不明確」 | テストの声に耳を傾けてください。テストしにくい=使いにくい。 |
| 「TDD は開発を遅くする」 | TDD はデバッグより速いです。実用的とはテストファーストのことです。 |
| 「手動テストのほうが速い」 | 手動では、エッジケースを証明できません。変更のたびに再テストすることになります。 |
| 「既存のコードにはテストがない」 | 既存のコードを改善しているのです。既存のコードにテストを追加してください。 |
危険信号 - 立ち止まり、最初からやり直してください
- テストより先にコードを書く
- 実装の後にテストを書く
- テストが即座に通る
- テストが失敗した理由を説明できない
- テストを「後で」追加する
- 「今回だけ」と正当化する
- 「すでに手動でテストした」
- 「後からのテストでも同じ目的を達成できる」
- 「大切なのは形式ではなく精神だ」
- 「参考として残す」または「既存のコードを適応させる」
- 「すでにX時間費やした。削除するのは無駄だ」
- 「TDD は教条的だ。私は実用的に行動している」
- 「これは違う。なぜなら…」
これらはすべて、こう意味します: コードを削除し、TDD で最初からやり直してください。
例: バグ修正
バグ: 空のメールアドレスが受け入れられる
RED
test "rejects empty email" do
user = User.new(email: "")
assert_not user.valid?
assert_includes user.errors[:email], "can't be blank"
end
RED の検証
$ bin/rails test test/models/user_test.rb
FAIL: expected "can't be blank" to be included in []
GREEN
class User < ApplicationRecord
validates :email, presence: true
end
GREEN の検証
$ bin/rails test test/models/user_test.rb
1 runs, 1 assertions, 0 failures, 0 errors
REFACTOR 必要に応じて、複数フィールドのバリデーションを抽出してください。
検証チェックリスト
作業を完了とする前に、以下を確認してください。
- 新しい関数/メソッドにはすべてテストがある
- 実装前に、各テストが失敗することを確認した
- 各テストは想定した理由(機能が未実装であるためであり、タイプミスではない)で失敗した
- 各テストを通すために必要最小限のコードを書いた
- すべてのテストが成功する
- 出力がクリーンである(エラーや警告がない)
- テストでは実際のコードを使っている(やむを得ない場合のみモックを使用)
- エッジケースとエラーを網羅している
すべての項目にチェックを付けられませんか?TDD を省略しています。最初からやり直してください。
行き詰まったとき
| 問題 | 解決策 |
|---|---|
| テストの方法がわからない | 望ましい API を書きます。先にアサーションを書きます。人間のパートナーに相談します。 |
| テストが複雑すぎる | 設計が複雑すぎます。インターフェースをシンプルにします。 |
| すべてをモックする必要がある | コードの結合度が高すぎます。依存性注入を使います。 |
| テストのセットアップが膨大 | ヘルパーを抽出します。それでも複雑ですか?設計をシンプルにします。 |
統合のデバッグ
バグが見つかりましたか?そのバグを再現する失敗するテストを書きます。TDD サイクルに従います。テストによって修正が検証され、再発が防止されます。
テストなしでバグを修正してはいけません。
テストのアンチパターン
モックやテストユーティリティを追加する際は、よくある落とし穴を避けるために @testing-anti-patterns.md を読んでください。
- 実際の動作ではなく、モックの動作をテストする
- 本番クラスにテスト専用のメソッドを追加する
- 依存関係を理解せずにモックする
モックを追加したりテストを書いたりする際は、Rails 固有のパターンについて @testing-strategy.md を読んでください。
最終ルール
Production code → test exists and failed first
Otherwise → not TDD
人間のパートナーの許可がない限り、例外は認められません。