テストの層とリファクタリング — Vibe Coding 時代の守り方
リファクタリング & プロダクト化 の「テストで今の動きを固定する」を、どのテストで・何を・どの順でまで具体化したページです。 合言葉は「判断は単体テストで、つながりは E2E で」。そして「同じ動きかどうかは、推測せず走らせて確かめる」。
AI にコードを書かせると、速く大量に変わります。人間が全部の行を読んで「壊れていない」と判断するのは無理です。だから壊れたら機械が赤くしてくれる状態を先に作り、その中で直します。
1. テストの層(何で何を守るか)
| 層 | 何を守るか | 速さ | 例 |
|---|---|---|---|
| 🧪 単体テスト | 1つの判断(計算・並べ替え・検証・整形)が正しいこと | 一瞬。何百本でも毎回回せる | node:test / vitest で純粋関数を呼ぶ |
| 🔗 結合テスト(ゴールデンテスト) | 部品を組み合わせた出力が変わっていないこと | 速い | CLI を実行し、出力を git に置いた「正解ファイル」と比べる |
| 🖥 E2E(API をまねる) | 画面の操作 → 表示の流れがつながっていること | 遅め(ブラウザを起動) | Playwright で画面を操作。サーバーの返事は偽物を返す |
| 🌐 E2E(本物につなぐ) | 本物のサーバー・DB・外部サービスまで通しで動くこと | 遅い・不安定になりやすい | 起動済みの開発サーバーに Playwright でつなぐ |
| 👀 起動して手で確かめる | 自動化していない部分。「本当に起動するか」 | 人間の時間を使う | yarn dev で開いて、変えた道筋を実際に通る |
下の層ほど速くて安く、たくさん書ける。上の層ほど本物に近いが、遅くて壊れやすい。だから「下を厚く、上は大事な道筋だけ」が基本形です(いわゆるテストピラミッド)。
2. 判断は単体で、つながりは E2E で
どちらか片方だけでは守れません。
- E2E で判断を網羅しようとしない
- 「空の入力」「境界の値」「壊れたデータ」を全部ブラウザ経由で試すと、遅すぎて誰も回さなくなる。
- 判断は純粋関数に切り出して、単体テストで「際」のケースを全部押さえる。
- 単体テストだけで「つながっている」とは言えない
- ルールを関数に切り出して30ケースのテストを書いても、呼び出し側がその関数を使っていなければ、画面は壊れたまま全部緑になる。
- 「ボタンを押したら、その関数を通って、結果が画面に出る」は E2E か、起動しての確認でしか分からない。
🧭 迷ったら:「この失敗は、値が間違う失敗か、つながりが切れる失敗か?」 値の間違い → 単体テスト。つながりの切れ目 → E2E。
3. 単体テスト — 判断を純粋関数に切り出して守る
- 判断と入出力を分ける:ファイル読み書き・通信・時刻は呼び出し側に残し、判断のルールだけを別ファイルの純粋関数にする。起動しないと届かないルールは、結局テストされません。
- 外部のものは引数で渡す:
now()(現在時刻)やファイルのパスを中で直接呼ばず、引数で受け取る。テストでは偽物を渡せます。 -
「際」のケースを書く(AI に頼むときもこの一覧を渡す)
種類 例 普通の入力 期待どおりの値 空 空文字・空配列・空オブジェクト null / undefined 値が無い 境界 最小・最大・1つずれ 不正な入力 型が違う・壊れたデータ 失敗 例外が投げられるべき場面 起きてはいけないこと 「これは通してはいけない」 過去のバグ 一度直したバグの再発防止 - 静かに間違うものから先に:例外を投げる関数は、壊れたら自分で知らせてくれます。怖いのはもっともらしい間違った値を返す関数(1つずれた番号、日付のずれ、甘すぎる入力チェック)。こちらを先にテストします。
4. 結合テスト — 出力を「正解ファイル」と比べる
CLI やプログラムの出力のように、組み合わせた結果が文字やファイルで出るものはゴールデンテストが向いています。
- 正しい出力をファイルとして git に置く(正解ファイル)。
- CI で実際の出力と比べ、1文字でも違えば赤。
- 出力をわざと変えたときだけ、正解ファイルを更新する。更新の差分は PR で人間が読む。
リファクタリング(動きを変えない変更)では、正解ファイルが1行も変わらないのが合格条件です。
5. E2E — 大事な道筋だけを、本物のブラウザで
道具は Playwright
Web アプリなら Playwright が定番です。本物のブラウザを動かし、クリック・入力・表示の確認を自動で行います。AI もよく知っている道具なので、書かせやすいです。
二本立てにする(MulmoClaude の例)
MulmoClaude では、E2E を目的の違う2種類に分けています(説明文書)。
e2e/(API をまねる) |
e2e-live/(本物につなぐ) |
|
|---|---|---|
| サーバー | テストが自動で起動 | 起動済みの開発サーバーを使う |
| サーバーの返事 | 全部を偽物で返す(mockAllApis(page) 必須) |
偽物なし |
| 向いているもの | 画面の流れ(開く・押す・表示される) | エージェントや外部連携まで通した動き |
| いつ回すか | PR ごと(分割して並列) | PR ごと(重い場面は省く)+ 毎日の定期実行 |
- API をまねる E2E は、返事が毎回同じなので安定して速い。PR のたびに回せます。偽物の返事をまとめた関数(
e2e/fixtures/api.ts)を最初に作り、全テストがそれを使うようにします。 - 本物につなぐ E2E は、偽物では分からない「本当につながっているか」を確かめる代わりに、遅くて不安定になりがちです。定期実行のワークフローでは、AI の応答が要る場面を飛ばして、それ以外を毎回回しています。
壊れにくい E2E を書くコツ
- 要素は
data-testidで取る:クラス名や画面上の位置で取ると、見た目を変えただけで壊れます(ロケーターの公式ガイド)。 - 固定時間の
sleepで待たない:「返事が届くまで待つ」(waitForResponse)や「表示されるまで待つ」で待つ。時間で待つと、遅い日に落ち、速い日には無駄に待ちます。 - 大事な道筋に絞る:ログイン、保存、決済のような「壊れたら困る流れ」を、うまくいく場合と、断られる場合の両方で。細かい分岐は単体テストに任せます。
- 定期実行には受け取り手を決める:夜中に赤くなっても誰にも届かないジョブは、そのうち全員が無視します。失敗したら issue を自動で作る、担当者に通知する、などを先に決めます。
6. 層を使ったリファクタリングの手順
初心者がつまずくのは「テストを書いてから直せと言われても、絡まっていてテストが書けない」ところです。そのときは外側から固めます。
- 🧭 変える場所が「みんなの通り道」か見る
- すべてのリクエストやすべての画面が通る場所(入口・共通の処理・よく呼ばれる関数)なら、テストが緑でも必ずアプリを起動して確かめる(手順 7)。
- 🖥 外側を E2E(かゴールデンテスト)で固定する
- 中身が絡まっていても、「この操作をするとこう表示される」は外から固定できます。今の動きをそのまま記録するテストです(特性テスト)。
- 今の動きにバグがあっても、まずはそのまま固定。直すのはリファクタリングとは別の変更です。
- 🔴 テストをわざと壊して、赤くなるか確かめる
- 条件を反対にする、一行消すなどして、テストが赤くなるのを見てから元に戻します。壊しても緑のままなら、そのテストは別のものを見ています。
- ✂️ 判断を1つだけ切り出す
- 1回の変更で1つ。切り出した純粋関数には、すぐに単体テスト(§3 の「際」のケース)を付けます。
- ⚖️ 「同じ動き」は走らせて確かめる
- 古いコードを捨てずに残し、新しいコードと同じ入力をたくさん流して結果を比べます。「読んだ限り同じはず」は証拠になりません。
- 比べ終わって古いコードを消す前に、「どんな入力が大事だったか」の一覧と「常に成り立つべき性質」(並び順が保たれる、合計が変わらない など)を、残すテストに書き写します。
- 🔌 呼び出し側を壊して確かめる
- 切り出した関数をわざと使わないように戻してみて、何かが赤くなるか。何も赤くならないなら、その関数は「誰も呼んでいない関数」としてテストされているだけです。E2E か結合テストを足します。
- 👀 起動して、変えた道筋を通る
- うまくいく場合と、断られる場合の両方。確かめられなかったことは「確かめていない」と PR に書きます。黙っていると「全部確かめた」と読まれます。
- 🙅 示せないなら、やらない
- 動きが同じだと示せない切り出しは、見送るのも正しい選択です。「やると何が良くなるか・何が危ないか・先に何を確かめる必要があるか」を issue か PR に書き残せば、次の人がそこから始められます。
⚠️ 目的は短くすることではありません。関数が短くなっても動きが危うくなるなら悪い取引、短くならなくても壊れたら赤くなる判断が1つ増えたなら良い取引です。
7. AI に頼むときの指示文(例)
このファイルをリファクタリングしたい。動きは変えない。
1. まず今の動きを固定するテストを書いて。画面の流れは Playwright の E2E(API は偽物を返す)、
判断の部分は単体テスト。
2. 書いたテストは、対象のコードをわざと壊して赤くなることを確認して、その結果を見せて。
3. 判断を1つずつ純粋関数として別ファイルに切り出して。1回の変更で1つ。
切り出した関数には、空・null・境界・不正な入力・失敗のケースを含む単体テストを付けて。
4. 切り出した関数を呼ばないように戻すと、どのテストが赤くなるかを見せて。
5. 動きが同じだと示せないところは切り出さずに、理由を書いて報告して。
AI が「テストは全部通りました」と言ったら、「壊したときに赤くなったか」を聞き返してください。通るだけのテストは、AI が一番よく作る失敗です。
8. チェックリスト
- 判断は純粋関数にあり、単体テストで「際」のケースまで押さえている
- CLI やファイル出力は、正解ファイルとの比較(ゴールデンテスト)で守っている
- 大事な道筋は E2E で、うまくいく場合と断られる場合の両方がある
- E2E は
data-testidで要素を取り、固定時間で待っていない - PR ごとの E2E はサーバーの返事を偽物にして安定させ、本物につなぐ E2E は定期実行で回している
- 定期実行が失敗したときの受け取り手が決まっている
- 新しいテストは、わざと壊して赤くなるのを確認した
- 切り出した関数は、呼び出し側を壊すと何かが赤くなる
- みんなの通り道を変えたときは、アプリを起動して確かめた。確かめていないことは PR に書いた
合わせて読む
- リファクタリング & プロダクト化(Vibe Coding時代の心得)
- Vibe Coding について
- 新規プロジェクト立ち上げチェックリスト(§12 テスト、CI で動かすチェック一覧)
- GitHub ActionsではじめるCI/CD
- AIハーネス入門