0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

テストを先に書かせてClaude Codeの暴走を止める — TDDで仕様を固定する実践ループ

0
Posted at

Claude Codeに機能をまるっと実装させると、こういう経験ありませんか。

  • 最初は良さそうに見えたのに、よく見ると要件の半分しか満たしていない
  • 「ついでに」関係ないファイルまで書き換えてくる
  • 修正を頼むと、直したはずの別の箇所が壊れる
  • 「動きました!」と言うが、実際に走らせるとエラーで落ちる
  • 5往復もすると、もう何が正しい挙動なのか自分でも分からなくなる

これ、モデルが馬鹿だからではありません。**「正解(仕様)が言葉でしか定義されていない」**のが原因です。

自然言語の仕様は曖昧で、解釈の幅があります。Claude Codeはその幅の中で「それっぽいもの」を生成し、あなたは出力を見て「いや、そうじゃなくて」と言葉で追記する。この往復が続くと、仕様はどんどんブレていきます。

これを止める一番シンプルな方法が TDD(テスト駆動開発) です。テストを先に書かせると、それが機械が検証できる仕様の固定になります。Claude Codeはもう「それっぽいもの」では合格できず、テストが緑になるまで実装し続けるしかなくなる。

この記事では、Claude CodeでTDDのループ(テスト→実装→リファクタ)を実際に回す手順を、コピペできるプロンプト付きで紹介します。すべて今日のセッションから試せます。


なぜテストが「暴走を防ぐ柵」になるのか

まず原理だけ押さえます。ここが腑に落ちると、あとの手順は自然に守れます。

自然言語の指示とテストコードの決定的な違いは、**「合否を誰が判定するか」**です。

仕様の形 合否の判定者 起きること
自然言語の指示だけ あなた(人間が目視) 解釈がブレる・見落とす・往復が増える
先に書いたテスト テストランナー(機械) 緑/赤で明確・Claudeが自分で確認できる

テストを先に書くと、次の3つが同時に手に入ります。

  1. 仕様が固定される — 「正しい挙動」がコードとして1つに定まる。あとから「やっぱりこうして」と言葉で揺らしても、テストを変えない限り基準はブレない
  2. Claudeが自己検証できる — 実装したら自分で npm test を走らせ、赤なら直す。あなたが目視でレビューする前に、Claude自身が最低ラインを担保する
  3. 修正が安全になる — リファクタや追加機能で既存挙動を壊すと、テストが赤くなって即バレる。「直したら別が壊れた」の無限ループから抜けられる

つまりテストは、Claude Codeにとっての**「越えてはいけない柵」**です。柵がないと自由に走り回って迷子になりますが、柵があれば走る範囲が決まる。これがTDDが暴走を防ぐ仕組みです。

補足: ここで言うTDDは「テストファースト」の意味で使っています。厳密なt-wada流の三角測量まで再現する必要はありません。「実装より先に、合格条件をコードで固定する」——この1点さえ守れば効果は出ます。


基本ループ: Red → Green → Refactor をClaudeで回す

TDDの基本サイクルは3ステップです。これをそのままClaude Codeに割り当てます。

1. Red    : 失敗するテストを書く(まだ実装がないので赤)
2. Green  : テストが通る最小限の実装を書く(赤→緑)
3. Refactor: 緑を保ったままコードを整える

重要なのは、この3ステップを1つの指示で一気にやらせないことです。一気にやらせると「テストも実装も同時に生成」してしまい、テストが実装に都合よく書かれてしまいます。それでは仕様の固定になりません。

ステップを分けて、各ステップで一度止めるのがコツです。

ステップ1: 失敗するテストだけを書かせる(Red)

最初の指示では実装を禁止します。テストだけを書かせ、実際に赤くなることを確認させます。

これから消費税計算関数 calcTax を作ります。まずTDDのRedフェーズだけやってください。

【今やること】テストだけを書く。実装は一切書かない。
【仕様】
  - calcTax(price, rate) を作る
  - price: 税抜価格(整数), rate: 税率(0.1 等の小数)
  - 戻り値: 税込価格。1円未満は切り捨て
  - price が負数なら Error を投げる
【出力】
  - tests/calcTax.test.ts にテストを書く
  - 正常系・境界値・異常系を網羅
  - テストを実行し、実装がないため失敗することを確認して報告

ここでのポイントは2つ。

  • 「実装は書くな」と明示する — これを言わないとClaudeは親切心で実装まで書いてしまい、テストが「実装ありき」になります
  • 「失敗を確認しろ」と言う — テストが赤くなることまで確認させると、「テストが本当に動く状態」であることが保証されます。緑になるはずのないテストがなぜか緑なら、それはテスト自体が壊れています

この時点で生成されたテストを必ず人間が読みます。ここがTDD最大の関門です(後述)。テストが仕様として正しいかを、ここで確定させます。

ステップ2: テストを通す最小実装を書かせる(Green)

テストが確定したら、次は実装。指示は驚くほどシンプルになります。

さっき確定した tests/calcTax.test.ts を通す実装を書いてください。

【制約】
  - テストを通すための最小限の実装にとどめる
  - テストファイルは変更しない(仕様は固定)
  - 実装後、必ず npm test を実行して全件緑になるまで直す

ここで効いてくるのが 「テストファイルは変更するな」 の一文です。これがないと、Claudeは実装がテストを通らないときに「テストのほうを実装に合わせて書き換える」という最悪の手を打ちます。仕様が実装に侵食される瞬間です。テストを凍結することで、Claudeは「実装を直す」以外の逃げ道がなくなります。

そして 「全件緑になるまで直す」。これでClaudeは自分でテストを走らせ、赤があれば自分で修正するループに入ります。あなたが目視する前に、機械的な合格ラインはClaude自身が担保してくれます。

ステップ3: 緑を保ったままリファクタさせる(Refactor)

テストが緑になったら、コードを整えます。ここでもテストが守ってくれます。

calcTax.ts をリファクタしてください。

【制約】
  - 振る舞いは一切変えない
  - tests/calcTax.test.ts は変更しない
  - リファクタの各ステップ後に npm test を走らせ、常に緑を維持
【観点】
  - マジックナンバーを定数化
  - 早期returnで分岐を浅く

リファクタは「振る舞いを変えずに構造を改善する」作業です。テストが緑のままなら振る舞いは保たれている、という保証があるからこそ安心して構造をいじれます。テストなしのリファクタはただの書き換えで、デグレが怖くて踏み込めません。


Before / After: 同じ機能を2通りで作らせる

「ログイン失敗を5回でアカウントロックする」機能を例に、TDDなしとありを比べます。

Before: いきなり実装させる

ログイン失敗が5回続いたらアカウントをロックする機能を作って

返ってくる実装は、一見それらしく動きます。が、こういう穴が後から発覚しがちです。

  • 「5回目でロック」なのか「5回失敗した次(6回目)でロック」なのか曖昧
  • ログイン成功で失敗カウントがリセットされるか不明
  • ロック後の挙動(解除条件・エラーメッセージ)が未定義
  • これらを指摘するたびに言葉で往復 → 直すと別の条件が壊れる

仕様が言葉のままなので、境界がすべて曖昧です。Claudeは曖昧な部分を勝手に埋め、あなたは出力を見て初めて「そこ違う」と気づく。後追いの修正合戦になります。

After: 先にテストで境界を固定する

ログイン失敗ロック機能を作ります。まずRedフェーズ。テストだけ書いてください。
実装は書かない。

【仕様 = テストで固定したい境界】
  - 失敗4回まではロックされない
  - 失敗5回ちょうどでロックされる
  - ロック中はカウント増加に関わらず弾く
  - 失敗が5回未満のときログイン成功でカウントは0にリセット
  - ロック中に正しいパスワードでもログインは拒否される
【出力】tests/accountLock.test.ts に上記を1ケースずつテスト化し、失敗を確認

テストを書かせた時点で、曖昧だった境界がすべてコード上の数値・分岐として確定します。「5回ちょうど」「4回まではOK」「成功でリセット」が、それぞれ独立したテストケースになる。この状態で実装を頼めば、Claudeはこの境界を1つも外せません。外したらテストが赤くなるからです。

観点 Before(言葉だけ) After(テスト先行)
境界条件 実装後に発覚 着手前に確定
仕様のブレ 往復のたびに揺れる テストで凍結され不変
デグレ検知 動かして初めて気づく テストが即赤くなる
レビュー対象 実装コード全部 まずテストだけ読めばよい
修正の収束 往復で発散しがち 緑になれば終わり

Afterの最大の利点は、レビューの対象が「実装全部」から「テストだけ」に変わることです。テストさえ正しければ、実装が緑になった時点で仕様は満たされています。読む量が激減します。


CLAUDE.mdに「TDDを標準にする」一文を入れておく

毎回プロンプトで「テストから書いて」と指示するのは面倒です。プロジェクトの CLAUDE.md に方針を書いておけば、Claudeが標準でTDDの順序を守るようになります。

## 実装の進め方

機能追加・バグ修正は原則テストファーストで進める。

1. まず失敗するテストを書き、テストが赤いことを確認してから止まる
2. テストの妥当性を私(人間)が確認するまで実装に進まない
3. 実装はテストを通す最小限にとどめ、テストファイルは勝手に変更しない
4. 実装後は必ずテストを実行し、全件緑を確認してから報告する
5. リファクタ時も常にテストを緑に保つ

ポイントは 「テストが赤い時点で一度止まる」 を明文化すること。これでステップ1とステップ2の間に人間のレビューが必ず挟まり、仕様の妥当性チェックが飛ばされなくなります。

CLAUDE.mdの細かい設計やHooksでの自動化は本記事の範囲外ですが、「TDDを標準動作にする一文」だけでも入れておくと、毎回の指示が一気に楽になります。


よくある失敗5つと回避策

TDDをClaude Codeで回すとき、特定の失敗パターンに必ずぶつかります。先に知っておけば避けられます。

失敗1: テストと実装を同時に書かせてしまう

一番多い失敗です。「テスト書いて実装して」と一息で頼むと、Claudeはテストを実装に都合よく書きます。実装が間違っていても、その間違いに合わせたテストが書かれるので両方緑になり、バグが温存されます。

回避策: Redフェーズ(テストのみ)とGreenフェーズ(実装)を必ず分け、間に人間のレビューを挟む。「実装は書くな」を毎回明示する。

失敗2: 生成されたテストを読まずに進む

TDDの効果は**「テストが正しい仕様である」**ことが前提です。テストが間違っていれば、Claudeは間違った仕様を完璧に実装します。これはTDDなしより危険——間違いが「緑」のお墨付きを得るからです。

回避策: Redフェーズで生成されたテストは必ず人間が読む。特に「期待値(expect)が本当に正しいか」を1ケースずつ確認する。ここだけは絶対に省略しない。テストを読む時間が、実装全体を読む時間より遥かに短いことを思い出してください。

失敗3: 実装が通らないとテストのほうを書き換える

実装がどうしても通らないとき、Claudeは「テストを実装に合わせて修正しました」とやることがあります。仕様が実装に負けた瞬間です。

回避策: 「テストファイルは変更しない」を制約に必ず入れる。テストを変える必要が本当にあるなら、それは仕様変更なので人間が判断する、と明確に分ける。

失敗4: 「動きました」を信じて実行を確認しない

Claudeは実装後に「テストが全て通りました」と報告することがありますが、実際にはコマンドを走らせていないことがあります。生成しただけで「通るはず」と推測で報告するケースです。

回避策: 「必ず npm test を実行し、その出力(緑の件数)を貼って報告して」と指示する。実行ログを見せさせれば、推測報告を弾けます。

失敗5: テストの粒度が荒すぎる / 細かすぎる

1つのテストで何もかも検証しようとすると、落ちたときに原因が分からない。逆に細かすぎると、些細な実装変更でテストが大量に壊れて保守が地獄になります。

回避策: 「1ケース1観点(境界値・正常系・異常系を別ケースに分ける)」を指示する。失敗1ケース=仕様1項目、の対応が取れていると、赤を見た瞬間にどの仕様が壊れたか分かります。

失敗 一言での回避策
1. テストと実装を同時生成 Red/Greenを分け、間にレビュー
2. テストを読まず進む Redで生成テストを必ず人間が読む
3. テストを書き換えて逃げる 「テストは変更しない」を制約に固定
4. 実行せず「通った」と報告 テスト実行ログの提出を必須化
5. テストの粒度が不適切 1ケース1観点を徹底

バグ修正にこそTDDが効く

新機能だけでなく、バグ修正でTDDは特に強力です。手順はこうです。

1. まず「そのバグを再現する失敗テスト」を書かせる(実装は触らない)
   → バグが本当にテストで赤くなることを確認
2. そのテストが緑になるよう実装を直させる
3. 全テストを走らせ、他が壊れていないことを確認

具体例:

「カートの合計が、数量0の商品を含むと NaN になる」というバグがあります。
まず実装は直さず、このバグを再現する失敗テストだけを書いてください。
数量0の商品を含むカートで合計を計算し、NaN にならず正しい数値になることを
期待するテストです。実行して、現状では赤くなることを確認して報告してください。

この手順の何が良いか。

  • バグが本当に「テストで捕まる形」になる — 再現テストが赤くなって初めて、バグの正体が確定する
  • 同じバグが二度と起きない — 修正後もこのテストが残るので、将来のリグレッションを防ぐ防波堤になる
  • 「直ったつもり」を排除できる — テストが緑になって初めて「直った」と言える。目視で「たぶん直った」がなくなる

バグ報告を受けたら、修正を頼む前に「まず再現テストを」。これを習慣にすると、修正の確実性が一段上がります。


まとめ: テストはClaude Codeへの「機械が読める仕様書」

TDDをClaude Codeで回す要点をまとめます。

ステップ やること 守るべき制約
Red 失敗するテストだけ書かせる 実装を書かせない・失敗を確認させる
(レビュー) 生成テストを人間が読む 期待値が正しいか1ケースずつ確認
Green テストを通す最小実装 テストファイルは変更させない・実行ログ提出
Refactor 緑を保ったまま整える 振る舞いを変えない・常に緑

Claude Codeが暴走するのは、賢くないからではなく、正解が言葉でしか定義されていないからです。テストを先に書かせれば、それが機械の読める仕様の固定になり、Claudeは「それっぽいもの」では合格できなくなります。柵の中でしか走れなくなる。

最初の一歩は簡単です。次にClaude Codeに何かを実装させるとき、いきなり「作って」と言う代わりに、**「まず失敗するテストだけ書いて。実装は書かないで」**と打ってみてください。生成されたテストを読んだ瞬間、自分の頭の中の仕様がいかに曖昧だったか気づくはずです。そこを固定するだけで、その後の往復が劇的に減ります。


補足: 試すための無料リポジトリ

本記事の内容を実際のプロジェクトで試すには、土台となるCLAUDE.mdとフォルダ構成があるとスムーズです。私が使っているスターター構成を無料で公開しています。

無料スターター(GitHub):
https://github.com/noguso245-jpg/claude-code-skills-starter

さらに踏み込んで、ワークフローやサブエージェント設計を「実行可能なスキルファイル」としてまとめたパッケージも用意しています。手元で /コマンド として呼び出せる形です。

まずは無料リポジトリから試して、もっと体系的に使いたくなったら検討してもらえれば十分です。記事の内容だけでも効果は出ます。


最新のTipsはXでも発信しています: @k___n___t_1125

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?