test-first について
test-firstは、失敗するテスト、最小限の実装、リファクタリングの順で変更を進める開発スキルです。機能追加、バグ修正、リファクタリング、動作変更を対象とし、例外には人の承認を求めます。
主な機能
- 01本番コードより先に失敗するテストを作成
- 02想定どおりの失敗と成功を確認
- 03テストを通すための最小限の実装
- 04テストを維持しながらリファクタリング
- 05テストでバグを再現し再発を防止
活用シーン
- 01テスト先行で新機能を開発
- 02再現テストを使ったバグ修正
- 03回帰を確認しながらリファクタリング
- 04検証済みテストを伴う動作変更
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ noobygains/ godmode --skill test-firstインストール後、Claude Code で/test-firstを入力して読み込まれたか確認します。
同梱ファイルtesting-anti-patterns.md
詳細
テストファースト開発
概要
最初にテストを書きます。失敗することを確認します。テストを通すための最小限のコードを書きます。
基本原則: テストが失敗するのを確認していないなら、そのテストが正しい動作を検証しているという根拠はありません。
例外なし。回避策なし。近道なし。
使用する場面
必須:
- 新機能
- バグ修正
- リファクタリング
- 動作の変更
例外(人間のパートナーに確認してください):
- 使い捨てのプロトタイプ
- 生成コード
- 設定ファイル
「今回だけテストファーストを省略しよう」と考えたくなったら、立ち止まってください。それは自分を正当化しているだけです。
最優先の指示
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
テストより先にコードを書きましたか?削除してください。最初からやり直します。
一切認めません:
- 「参考資料」として残さない
- テストを書きながら「調整」しない
- ちらりと見ることもしない
- 削除とは、削除することです
テストから新たに作り直してください。それだけです。
レッド・グリーン・リファクタリング
digraph tdd_loop {
rankdir=LR;
red [label="RED\nWrite failing test", shape=box, style=filled, fillcolor="#ffcccc"];
check_red [label="Fails\ncorrectly?", shape=diamond];
green [label="GREEN\nMinimal code", shape=box, style=filled, fillcolor="#ccffcc"];
check_green [label="All tests\npass?", shape=diamond];
refactor [label="REFACTOR\nClean up", shape=box, style=filled, fillcolor="#ccccff"];
next [label="Next", shape=ellipse];
red -> check_red;
check_red -> green [label="yes"];
check_red -> red [label="wrong\nfailure"];
green -> check_green;
check_green -> refactor [label="yes"];
check_green -> green [label="no"];
refactor -> check_green [label="stay\ngreen"];
check_green -> next;
next -> red;
}
RED - 失敗するテストを書く
期待される動作を示す、最小限のテストを1つ書きます。
```typescript test('retries failed operations up to 3 times', async () => { let callCount = 0; const unstableOp = () => { callCount++; if (callCount < 3) throw new Error('transient failure'); return 'done'; };const outcome = await withRetry(unstableOp);
expect(outcome).toBe('done'); expect(callCount).toBe(3); });
説明的な名前で、実際の動作をテストし、関心事が1つだけ
</Good>
<Bad>
```typescript
test('retry works', async () => {
const spy = jest.fn()
.mockRejectedValueOnce(new Error())
.mockRejectedValueOnce(new Error())
.mockResolvedValueOnce('done');
await withRetry(spy);
expect(spy).toHaveBeenCalledTimes(3);
});
曖昧な名前で、実際の動作ではなくモックとのやり取りを検証している
基準:
- テストごとに動作は1つ
- 説明的な名前
- 実際のコードを使用する(避けられない場合に限りモックを使用)
RED を確認 - 失敗を見届ける
必須です。絶対に省略しないでください。
npm test path/to/test.test.ts
次の点を確認します:
- テストが失敗する(エラー終了ではない)
- 失敗メッセージが想定どおりである
- 機能が実装されていないために失敗する(誤字が原因ではない)
テストが成功しますか? 既存の動作をテストしています。テストを書き直してください。
テストがエラーになりますか? エラーを修正し、正しく失敗するまで再実行してください。
GREEN - 最小限のコード
テストを通すための、可能な限りシンプルなコードを書きます。
```typescript async function withRetry(fn: () => Promise): Promise { for (let attempt = 0; attempt < 3; attempt++) { try { return await fn(); } catch (e) { if (attempt === 2) throw e; } } throw new Error('unreachable'); } ``` テストを通すのに必要な分だけ ```typescript async function withRetry( fn: () => Promise, opts?: { maxAttempts?: number; backoffStrategy?: 'linear' | 'exponential'; onAttempt?: (n: number) => void; } ): Promise { // YAGNI } ``` 過剰に設計されている機能を追加したり、無関係なコードをリファクタリングしたり、テストが求める以上に「改善」したりしないでください。
GREEN を確認 - 成功を見届ける
必須です。
npm test path/to/test.test.ts
次の点を確認します:
- テストが成功する
- 他のテストも引き続き成功する
- 出力がクリーンである(エラーや警告がない)
テストが失敗しますか? テストではなくコードを修正してください。
他のテストが失敗しますか? 今すぐ修正してください。
REFACTOR - 整理する
成功してからのみ、次のことを行います:
- 重複をなくす
- 名前を改善する
- ヘルパーを切り出す
テストを成功した状態に保ちます。新しい動作を追加しないでください。
繰り返す
次の動作に対する、次の失敗するテストを書きます。
品質基準
| 品質 | 良い例 | 悪い例 |
|---|---|---|
| 焦点が絞られている | 1つのこと。「and」が名前に入っていますか?分割してください。 | test('validates email and domain and whitespace') |
| 説明的 | 名前から動作が分かる | test('test1') |
| 意図が明確 | 望ましい API を示す | コードが何をすべきかを分かりにくくする |
シーケンスが重要な理由
「動作を確認するために、あとでテストを書きます」
コードのあとに書いたテストは、すぐに通ります。すぐに通ることは、何の証明にもなりません。
- 間違ったものを検証している可能性があります
- 動作ではなく、実装の詳細を検証している可能性があります
- 見落としたエッジケースを検出できない可能性があります
- 失敗を検出する様子を確認できません
テストファーストなら、失敗を確認できます。これにより、テストが実際に何かを検証していると証明できます。
「エッジケースはすべて、すでに手動でテストしました」
手動テストは場当たり的です。すべて網羅したと思っていても、次の問題があります。
- 何をテストしたか記録が残りません
- コードを変更したときに再実行できません
- プレッシャーがかかると、ケースを飛ばしがちです
- 「試したときは動いた」は、網羅的な検証ではありません
自動テストは体系的です。毎回同じ手順で実行されます。
「X 時間分の作業を削除するのは無駄です」
サンクコストの誤謬です。時間はすでに費やされています。今できる選択肢は次のとおりです。
- 削除してテストファーストで作り直す(さらに X 時間、高い信頼性)
- そのまま残してテストを後付けする(30 分、低い信頼性、不具合が発生する可能性が高い)
本当の無駄は、信頼できないコードを残すことです。適切なテストのない動作するコードは、技術的負債です。
「テストファーストは独善的です。実用的であるには、柔軟に対応すべきです」
テストファーストこそ実用的です。
- コミット前に不具合を検出できます(あとでデバッグするより速い)
- リグレッションを防げます(テストがすぐに破損を検出します)
- 動作を文書化できます(テストによってコードの動作を示せます)
- リファクタリングが可能になります(自由に変更でき、テストが問題を検出します)
「実用的」な近道は、本番環境でのデバッグにつながり、結果的に時間がかかります。
「あとからテストしても同じ目的を達成できます。大切なのは形式ではなく精神です」
いいえ。あとから書くテストが答えるのは「これは何をするのか?」です。先に書くテストが答えるのは「これは何をするべきか?」です。
あとから書くテストは、実装に偏ります。必要なものではなく、作ったものを検証します。思い出せるエッジケースは確認しますが、発見したエッジケースは確認しません。
先に書くテストは、実装前にエッジケースの発見を促します。あとから書くテストは、すべてを覚えていたかを検証します(覚えていません)。
あとから30分かけてテストを書いても、テストファーストにはなりません。カバレッジは得られますが、テストが機能するという証明は失われます。
認知の落とし穴
| 正当化 | 実際にはこうです |
|---|---|
| 「テストするほど複雑ではない」 | 簡単なコードでも壊れます。テストは30秒で書けます。 |
| 「あとでテストします」 | すぐに通るテストは、何の証明にもなりません。 |
| 「あとからテストしても同じことを達成できる」 | あとから書くテスト=「これは何をするのか?」先に書くテスト=「これは何をするべきか?」 |
| 「すでに手動でテストしました」 | 場当たり的なテストは体系的ではありません。記録がなく、再実行もできません。 |
| 「X 時間分の作業を削除するのは無駄です」 | サンクコストの誤謬です。未検証のコードを残すことは技術的負債です。 |
| 「参考用に残して、先にテストを書きます」 | それを参考にして調整してしまいます。それはあとからのテストです。削除するなら、完全に削除してください。 |
| 「まず調査する必要があります」 | かまいません。調査結果は破棄してから、テストファーストで始めてください。 |
| 「テストしにくいので、スキップすべきです」 | テストからのシグナルに耳を傾けてください。テストしにくいのは、使いにくいということです。設計をシンプルにしてください。 |
| 「テストファーストでは時間がかかります」 | テストファーストはデバッグより速いです。実用的であるとは、テストファーストを実践することです。 |
| 「手動テストのほうが速いです」 | 手動テストでは、エッジケースを確認できません。変更のたびに再テストが必要です。 |
| 「既存のコードにはテストがありません」 | 改善するのはあなたです。既存のコードにもテストを追加してください。 |
ガードレール — 立ち止まってやり直す
- テストより先にコードを書く
- 実装後にテストを書く
- テストがすぐに通る
- テストが失敗した理由を説明できない
- テストを「あとで」追加する
- 「今回だけ」と正当化する
- 「すでに手動でテストしました」
- 「あとからのテストでも同じ目的を果たせます」
- 「大切なのは形式ではなく精神です」
- 「参考用に残す」または「既存のコードを流用する」
- 「すでに X 時間を費やしたので、削除するのは無駄です」
- 「テストファーストは独善的です。実用的にやっているだけです」
- 「今回は違うので……」
これらはすべて、コードを削除してテストファーストでやり直すべきだというサインです。
例:バグ修正
バグ: 空のメールアドレスがフォームで受け付けられる
RED
test('rejects empty email', async () => {
const result = await processForm({ email: '' });
expect(result.error).toBe('Email required');
});
RED を確認
$ npm test
FAIL: expected 'Email required', got undefined
GREEN
function processForm(data: FormData) {
if (!data.email?.trim()) {
return { error: 'Email required' };
}
// ...
}
GREEN を確認
$ npm test
PASS
リファクタリング 必要に応じて、複数のフィールドに対応するバリデーションパイプラインを切り出します。
完了チェックリスト
作業を完了と宣言する前に、次を確認してください。
- 新しく追加したすべての関数/メソッドにテストがある
- 実装前に、各テストが失敗することを確認した
- 各テストは想定どおりの理由(機能が未実装であるためで、タイポではない)で失敗した
- 各テストを通すための最小限のコードを書いた
- すべてのテストが通る
- 出力に問題がない(エラーも警告もない)
- テストでは実際のコードを使っている(避けられない場合にのみモックを使う)
- エッジケースとエラー経路を網羅している
すべての項目にチェックを付けられませんか?テストファーストを実践できていません。最初からやり直してください。
行き詰まったとき
| 問題 | 解決策 |
|---|---|
| テストの書き方がわからない | あったらよいと思う API を書いてください。まずアサーションを書きます。人間のパートナーに相談してください。 |
| テストが複雑すぎる | 設計が複雑すぎます。インターフェースをシンプルにしてください。 |
| 何でもモックしなければならない | コードの結合度が高すぎます。依存性注入を使ってください。 |
| テストのセットアップが大がかりすぎる | ヘルパーを切り出してください。それでも大変ですか?設計をシンプルにしてください。 |
統合のデバッグ
バグを見つけましたか?そのバグを再現する失敗テストを書いてください。テストファーストのサイクルに従ってください。テストによって修正を検証し、再発を防止できます。
テストなしでバグを修正してはいけません。
テストのアンチパターン
モックやテストユーティリティを追加するときは、よくある落とし穴を避けるため、@testing-anti-patterns.md を読んでください。
- 実際の動作ではなく、モックの動作を検証する
- 本番クラスにテスト専用のメソッドを追加する
- 依存関係の連鎖を理解せずにモックを使う
最終ルール
Production code -> test exists and failed first
Otherwise -> not test-first
人間のパートナーの許可がない限り、例外は認められません。