mcpField
T
test-first

test-first

github.com/noobygainsテスト・品質11561 回インストール5 日前に更新
リポジトリ

test-first について

test-firstは、失敗するテスト、最小限の実装、リファクタリングの順で変更を進める開発スキルです。機能追加、バグ修正、リファクタリング、動作変更を対象とし、例外には人の承認を求めます。

主な機能

  1. 01本番コードより先に失敗するテストを作成
  2. 02想定どおりの失敗と成功を確認
  3. 03テストを通すための最小限の実装
  4. 04テストを維持しながらリファクタリング
  5. 05テストでバグを再現し再発を防止

活用シーン

  1. 01テスト先行で新機能を開発
  2. 02再現テストを使ったバグ修正
  3. 03回帰を確認しながらリファクタリング
  4. 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

人間のパートナーの許可がない限り、例外は認められません。

コメント