見出し画像

自作Reactコンポーネントのユニットテストを、Claude Codeに8分で4パターン固定24ケースにしてもらった夜

個人開発のReactコンポーネントを書き終えた直後に、テストで手が止まる夜がありました。

UIのstateもハンドラも動作確認済み。あとはユニットテストを整えるだけ、なんですが、いざファイルを開くと「何から書けばいいんでしたっけ」となる時間帯、結構あります。

手癖で書き始めると、正常系だけ8本ぐらいずらっと並んで、境界値と回帰の観点がごっそり抜ける。カバレッジは上がるのに、書き終わった後の安心感が薄い仕上がりになってしまいます。

そこで今夜は、Claude Codeにテスト設計の枠だけ先に渡して、4パターン固定・順序固定・合計24ケースで一気に書き起こしてもらいました。所要時間は8分以内。手作業だと60〜90分かかっていた工程が、桁ひとつ手前まで縮みました。


こういう夜、ありませんか

自作アプリの入力フォーム系コンポを書き終えて、テストファイルを開いたところで筆が止まる。フォームの構造は覚えているし、モックする依存も把握している。それでも「何をテストすべきか」の設計だけが、なかなか出てきません。

試しに手癖で it を並べ始めると、"送信できる"、"エラーメッセージが出る"、"必須欄が空だと止まる"、こういう正常系の連続で埋まっていきます。カバレッジは順調に上がるのですが、境界値と例外系がごっそり抜けている。

境界値というのは、たとえば入力が0文字のとき、上限桁数のとき、リストが0件のとき、1件のとき、複数件のときの動きです。異常系というのは、propsに想定外の型が来たときや、内部で呼んでいるAPIが失敗したときの挙動。回帰テストというのは、以前のバージョンで出た既知バグを再現しないための固定ケース。

この4つの観点それぞれに、何ケース必要かの相場感が自分の中に無いのが、手が止まる正体だったようです。24ケースを白紙から並べるとどうしても偏る。だったらAIに枠を先に決めさせれば良い、というのが今回の試行です。

試したこと

Claude Codeに、対象コンポーネントのTSXコード全文と、テスト構造の要件を一枚のプロンプトで渡しました。要件の中心は「4パターンを順序込みで固定」と「各パターンのケース数を先に振る」の2つです。

パターンは (1)正常系(happy path) 8ケース、(2)境界値(min/max/空/1件/複数件) 6ケース、(3)異常系(props不正/例外/API失敗) 6ケース、(4)回帰テスト(既知バグの再現防止) 4ケース、で計24ケース。順序変更・追加・削除は禁止で固定にしました。

加えて、対象コンポに書かれていない仕様(存在しないpropや、明示されていないイベント名)は絶対に推測させず、仮定した箇所にはコメント末尾に (要確認) タグを付けさせる指示を入れました。これはClaude Codeの"良さそうに見える推測"をそのまま通さないための保険です。

モックが必要な依存(API・ルーター・グローバル状態・タイマー)は、各 describe の冒頭にブロックコメントで列挙させる形にして、テスト本体で急に vi.mock が出てきて驚くパターンも潰しました。

使ったプロンプト

以下のReactコンポーネントに対して、Vitest + React Testing Library で
ユニットテスト24ケースを4パターン固定で生成してください。

対象コンポーネント:
(ここに対象コンポーネントのTSXコード全文を貼る。propsの型定義と
ハンドラの実装、useEffectの依存配列まで含めて一緒に貼る)

必須要件(すべて守ってください):

1. 4パターンをこの順で固定してください。追加・削除・順序変更は禁止です。
   (1) 正常系(happy path) 8ケース
   (2) 境界値(min/max/空/1件/複数件) 6ケース
   (3) 異常系(props不正/例外/API失敗) 6ケース
   (4) 回帰テスト(既知バグの再現防止) 4ケース

2. 各テストケースは describe / it の入れ子で書き、it の中は
   "should (現在形の期待動作)" で始めてください。
   日本語コメントは各 it の直上に1行、"// 期待: (期待動作)"
   の様式で統一してください。

3. モックが必要な依存(API・ルーター・グローバル状態・タイマー)は、
   各パターン冒頭 describe のブロックコメントで列挙し、
   "// モック対象: (対象1), (対象2), ..." としてください。

4. 対象コンポに書かれていない仕様(存在しないprop・値・イベント名)は
   絶対に推測しないでください。テスト内で仮定した仕様には、
   その行のコメントに (要確認) タグを付けてください。

5. アクセシビリティに関わる問い合わせは、必ず screen.getByRole または
   screen.getByLabelText を使ってください。data-testid は最終手段
   としてのみ許可し、使う場合はコメントに (要確認) タグを付けてください。

6. 各パターン末尾に "// (小計) パターン(番号): (ケース数)" のコメント行を
   1本入れてください。ケース数はちょうど 8 / 6 / 6 / 4 にしてください。

7. 最終行に1行の(セルフチェック)を入れてください。様式は固定で以下:
   "// (セルフチェック)4パターン=(1)/(2)/(3)/(4)全部出力済/合計24ケース/(要確認)タグN箇所"

8. 禁止事項:
   - expect(...).toBeTruthy() だけの曖昧な検証は禁止。
     必ず値・テキスト・呼び出し引数まで検証してください。
   - ランダム値・現在時刻に依存するテストは、シード固定または
     vi.setSystemTime で必ず固定してください。
   - 1つの it に複数の期待動作を詰め込むのは禁止。1 it = 1 期待動作。

出力形式:
- .test.tsx ファイル1本のコードブロック
- import 文込みでコピペで実行できる完成形
- 各パターン末尾の(小計)コメントと、末尾のセルフチェック行を含む

差し込むのは対象コンポーネントのTSXコード1ファイル分だけです。propsの型定義とハンドラの実装、useEffectの依存配列まで含めて一緒に貼ります。コンポの中で呼んでいる自作フックは、名前と戻り値の型だけコメントで付記すれば十分でした。

実行はClaude Codeの対話ウィンドウにコンポ全文を貼り付けて、続けて上のプロンプトを貼るだけ。カスタムsub-agent化は今回はしていません。単発の依頼で通しています。

戻ってきたもの

出力は .test.tsx ファイル一本のコードブロックで返ってきました。文字数は import 文込みで約1,900字。describe 4層のネスト、it 24本、モック対象は3種類(fetch / router / timers)が冒頭に明示されていました。

(要確認) タグは2箇所付いていました。1つは onSubmit 中の disable フラグの挙動、もう1つはエラー時のフォーカス遷移。どちらもTSXコードの範囲では確定できない仕様だったので、これは私の側で本体を見直して埋める前提の材料になりました。

各パターン末尾には (小計) の行が入っており、(1)8 / (2)6 / (3)6 / (4)4 のちょうど24ケース。最終行のセルフチェック行も様式通り出力されていました。

// (小計) パターン(1): 8ケース
// (小計) パターン(2): 6ケース
// (小計) パターン(3): 6ケース
// (小計) パターン(4): 4ケース
// (セルフチェック)4パターン=(1)/(2)/(3)/(4)全部出力済/合計24ケース/(要確認)タグ2箇所

ぱっと見の印象で言うと、境界値(2)の"リストが0件のときに何も描画しないこと"と、異常系(3)の"API失敗時にエラーメッセージが出ること"は、自分で書いていたら忘れていたはずのケースでした。

効いた指示・工夫したポイント

  • (1) 4パターンを順序込みで固定:どのパターンが薄いかがすぐ分かる

  • (2) 各パターンにケース数(8/6/6/4)を先に振る:全体の粒度が揃う

  • (3) 対象コンポにない仕様は(要確認)タグを義務化:推測でテストが甘くなるのを防ぐ

  • (4) 1 it = 1 期待動作を明示:後で失敗時のログが読める

  • (5) セルフチェック行を末尾に様式固定:ケース数が合っているかその場で見える

特に(2)が効いた実感があります。24ケースを白紙から出させると必ず偏るのですが、8 / 6 / 6 / 4 と先に振っておくと、Claude Codeが"回帰は4ケースまで、境界値は6ケース、正常系は8ケース"と自然に枠内で書き分けてくれます。ケース数の数字自体は状況で変えてよいのですが、"最初に振っておく"というルールは固定しています。

(3) の (要確認) タグも便利でした。テストコードのコメントに (要確認) が付いた行を grep するだけで、"自分がまだ仕様を決めていない箇所"の一覧になります。テストを書くつもりで始めたのに、実は仕様書きの続きだったパターン、結構あるようです。

カスタマイズ・他用途への応用

  • Vue3コンポのVitest:describe / it の構文はそのままで、Testing Libraryの箇所を @vue/test-utils に差し替えるだけで動きます

  • バックエンドAPIのJestテスト:Testing Library の箇所を Supertest に置換し、パターンを (1)正常応答 / (2)境界値 / (3)エラー応答 / (4)回帰 に読み替え

  • Python関数の pytest:describe / it を pytest.mark.parametrize + class に置き換え、モック指定は pytest-mock を使う旨をプロンプト本文に書き足す

  • E2E Playwright:ケース総数を8ケース程度に減らし、粒度をユーザー操作の一連の流れに合わせて (1)〜(4) の意味を"シナリオ単位"で再定義

  • チーム共有の"最低限テスト仕様書"雛形:TypeScriptコード出力を廃止して"日本語で何をテストするか列挙"に切り替えると、レビュー用の仕様書テンプレになります

使い心地・気づき

  • 4パターン固定にすると"何を書き忘れたか"が明示されます。特に回帰テストは意識しないと0本になりがちなので、枠として先に予約しておく価値がありました

  • (要確認) タグが3個以上出るときは、対象コンポ側のprops型定義や関数シグネチャが不足しているサインとして扱えます。テスト設計より先にコンポの型を厚くする回、が入り口になります

  • 逆に(要確認) ゼロは、対象コンポの仕様が全部明示された良い状態、と読めます

  • ケース数を先に振ると"境界値を6ケースも書けない"みたいな詰まり方をする瞬間があり、そのときは分岐の設計そのものを見直したくなります

  • Vitestの実行時間は24ケースで3秒未満だったので、CIに乗せても軽く済みそうです

  • ひとつ弱点があって、fetch のモックが複雑になる異常系(3)は、Claude Codeが提示するモック手法がプロジェクトの慣行と食い違うことがあり、この部分は目視で書き換えました

所要時間のまとめ

  • 対象TSXコードの貼り付け:1分

  • プロンプト実行と応答待ち:2分

  • (要確認) タグの目視補正と仕様埋め合わせ:3分

  • vitest 実行と失敗ケース1本の修正:2分

合計で8分以内。手作業で同じ規模のテストを書くと60〜90分かかっていた作業なので、桁ひとつ手前まで縮む計算です。テストは着手前のハードルが高い作業なので、"着手までの時間"を減らせたことが体感の効きとしては大きい印象でした。

結びに

ふむ、テストは"書かないとバグる"以外の理由でも手が動きにくい作業なので、パターン数とケース数を先に決めてしまう枠組みは結構効きました。カバレッジ数字は"手を動かした量"の指標にはなるのですが、"何を漏らしていないか"は別の物差しなので、4パターン固定の枠を最初に敷いておくと、その物差しが1本増えます。

ひとつ副産物として気づいたのは、1本のテストファイルに (要確認) タグが2個以上出た時、それはコンポ側の設計ドキュメントが薄いサイン、として扱えることです。テストを書く夜が来る前に、コンポの型定義を厚めに書いておくと、AIに任せた時の (要確認) タグが減ります。テストコードは、書きながら仕様を確定させる作業でもあるようです。


#ClaudeCode #プロンプトエンジニアリング #React #ユニットテスト #Vitest #個人開発 #フロントエンド #エンジニア #テスト自動化

いいなと思ったら応援しよう!