この記事は、連載「AIの意見を聞かない技術」の第 6 回です。うまくいかない症状と、その対処を一つずつ並べています。各回はファイルやスクリプトを一つ置けば完結します。その仕組みがなぜ要るのかは、置いたあとに解説を読めば分かります。連載の全体像と各回の一覧は序論にあります。
今回は、AI から来る目視確認の依頼の話です。確認そのものは、いまも続いています。やめたのは、が画面を開いて目で見ることです。AI は作業の最後に、残っているのは実機での目視確認だけだと書いてきました。は、その依頼を断っています。代わりに置いたのは、合否はプログラムが決めるという形のスモークテスト 1 本です。スモークテストというのは、実際のブラウザで主要な操作を最後まで通して、サービスが壊れていないかを見るテストのことです。
前作「読まない技術」の第 12 回で、は、AI が仕上げた作業結果そのものを読まずに済ませる形を置きました。今回は、その作業結果の目視確認を、AI から頼まれたときの話です。
まず一つだけ、数えてみてください。
直近のセッションで、AI が目視確認をあなたに残して作業を終えた回数です。
そのうち、変更した行に単体テストが付いていたのは、何回でしたか。
CC BY 4.0
AI に確かめ方を任せると、AI は目視確認とスモークテストを選んだ
の手元の本番プロジェクト——typingtube という、YouTube の音楽動画でタイピング練習ができる Web サービスです——では、AI が作業を終えるときの最後の一言は「残るのは実機の目視 2 点だけ」でした。AI が次のセッションへ渡すメモにも、「残るのは実画面の目視(人)だけ」と書かれていました。この一言は、が一度も画面を開かなくても、AI から届きます。
AI が選んだ確かめ方は、目視確認のほかにもありました。画面の演出を作ったとき、AI は、演出のロジックをスモークテストだけで確かめていました。関数の単体テストは 0 件でした。スモークテストは、演出が出る条件を 1 つ確かめるのにも、ブラウザを起動し、ページを開き、操作を最後まで通します。実装を試しながら直すあいだは、直すたびにこれを繰り返します。スモークテストを見直すと、その大半は、演出が出る条件や、同じ演出を 2 度出さない処理のような細かいロジックを確かめるもので、関数を直接呼ぶ単体テストで確かめることのできるものでした。そうしたロジックを単体テストへ移し、スモークテストには、画面の配置の切り替えのように、ブラウザでしか確かめることのできないものだけを残しました。それからは、直すたびに走らせるのは単体テストになり、スモークテストも短くなりました。
AI が使うトークンは、目視確認なら 0、スモークテスト 1 回なら返ってくる数行の出力を読む分、変更した行ごとの単体テストなら、テストを書く分と、走らせる分と、結果を読む分でした。
AI が作業の最後に、「残るのは実機の目視確認だけ」と書いてきました。このとき、あなたなら次のどちらを選びますか。
- 画面を開いて、目で見て確かめる
- 確かめるものに合ったテスト(ロジックは単体テスト、ブラウザでしか確かめることのできないものはスモークテスト)を置き、合否をプログラムに決めさせる
答えは、テストを置くほうです。依頼が来たのは、AI の側に、走らせて合否を読むことのできるテストが無く、仕事が終わったように見えることが、唯一の手がかりだったからです。あなたが画面を開いて見ても、見た結果はどのツールの結果にも入らないので、AI には届きません。見たことはどこにも記録されず、次のセッションのAIが読むのは、メモに残った「残るのは実画面の目視(人)だけ」のほうです。テストなら、AI が自分で走らせて合否を読み、落ちれば自分で直します。ロジックを単体テストで確かめるのは、冒頭の演出のように、スモークテストで確かめると、直すたびにブラウザを起動して、操作を最後まで通すことになるからです。
理解したことは一文にできます。
目視確認の依頼は、AI の側に走らせて合否を読むことのできるテストが無いまま、合否の判断を人に渡している。だから依頼を断り、確かめるものに合ったテストで、合否をプログラムが決める形にする。
なぜ、目視確認の依頼は、合否の判断を人に渡しているのか
Claude Code のドキュメントは、ツールを使うたびに返ってくる情報がループへ戻り、次の判断の材料になると書いています。人が画面を開いて目で見たものは、どのツールの結果にも入りません。
Claude Code のベストプラクティスによると、Claude が止まるのは、仕事が終わったように見えたところです。走らせることのできる自動テストが無ければ、「終わったように見える」ことが唯一の信号になり、あなたが確かめる役になります。目視確認の依頼は、この形で出てきます。AI の側に、走らせて合否を読むことのできる自動テストが無い、という形です。Claude のプロンプトの指針も、自律的な作業が長くなるほど、AI は人の継続的なフィードバック無しで正しさを確かめる必要があると書いています。
Claude Code のドキュメントは、セッションは独立していて、前のセッションの会話履歴を持たないと書いています。が画面を開いたかどうかは、どのファイルにも記録されません。次のセッションの AI が読むのは、前のセッションの AI が書き残したメモの「残るのは実画面の目視(人)だけ」のほうです。
ベストプラクティスのページは、AI が走らせる自動テストの例も挙げています。テストスイート、ビルドの終了コード、lint、出力を期待値と比べるスクリプト、設計図と比べたブラウザのスクリーンショットです。自動テストに求める条件は一つで、AI が会話の中で読むことのできる信号を返すことです。ページには、AI がそれを自分で走らせ、結果を自分で読み、通るまで直す、という使い方が書かれています。
仕組み: スモークテストを 1 本書き、合否は DB で決める
変更した行を見る単体テストと、その行をわざと壊して単体テストが落ちるかを確かめる変異は、前作の第 3 回でが置いたものです。単体テストと変異で合否を決めたあとにも、確かめるものが残ります。そこに置くのが、スモークテスト 1 本です。例は、単語帳の対戦です。2 人が同じ部屋に入り、同じ順番で出題される単語を打ちます。「2 人に同じ順番で出題されているか」は、が画面を目で見ても分かりません。2 人の画面はそれぞれ独立して進むので、表示中の単語は簡単にずれます。実際、最初のスモークテストは、2 つの画面に出ている単語を突き合わせていました。出題順は同じだったのに、このテストは不一致を返しました。先に画面を開いた側が 1 語目で時間切れになり、表示が 1 語ずれていたからです。表示は制限時間が来ると先へ進みますが、打った順は、出題された順のままです。
スモークテストは、テスト用のデータを作り、2 人ぶんのブラウザで対戦を最後まで打ち、終わったらデータを消します。ブラウザは、Docker の中で、Playwright を使って動かします。
# lib/tasks/dev_smoke.rake(手元のコードを短くしたもの)— スモークテストが終わったあと、画面ではなく DB を見る
orders = VocabularyPlayLog.where(battle_room_id: room.id).map do |log|
log.entry_play_logs.in_play_order.pluck(:vocabulary_entry_id)
end
puts "entry_order_match=#{orders.uniq.size == 1}" # 2 人に同じ順で出たか
plan = VocabularyBattleEntryOrder.call(version: record.vocabulary_deck_version,
play_mode: "shuffle", shuffle_seed: record.shuffle_seed)
server_ids = plan.entries.map(&:id)
puts "server_order_match=#{server_ids == orders.first}" # サーバーが決めた順と同じか
結果の形は、「Smoke FAILED (playwright=1 verify=1)」のような 2 つの数です。playwright の数は、ブラウザの操作が最後まで通ったかを、verify の数は、DB の判定がそろったかを表します。どちらかが落ちれば、結果は失敗です。スモークテストを走らせるかどうかを決め、走らせ、この 2 つの数を読むまでを、AI が自分で行います。ただし、何をスモークテストで確かめるかを決めるのもAIです。冒頭の演出のロジックを、スモークテストだけで確かめていたのも、AI でした。また、この 2 つの数を決めている行があるのは、AI が編集することのできるファイルの中です。同じ対戦でも、DB にデータを残さないモードが一つあります。そのモードでは、いまもスモークテストが画面を見ています。
スモークテストから漏れた不具合は、本番で踏んだ人から届く
スモークテストが見るのは、そこに書いた場面だけです。書かなかった場面の不具合は、そのまま本番に出ます。その不具合は、が確かめに行く代わりに、踏んだ人から届く経路で受けています。経路は、不具合報告のフォームです。報告は、領域(タイピング / 対戦 / 歌詞表示…)と種別(不具合 / 要望 / 歌詞の間違い)で分類され、未読・既読・対応済みの状態を持ちます。が見るのは、溜まった一覧だけです。
CC BY 4.0 はここまで
確認しなくなったもの
画面です。ローカル環境の画面も本番環境の画面も、は開いて回ることをやめました。AI から目視確認の依頼が来ても、が返すのはいつもの「次の作業を進めてください」です。代わりに確かめているのは、単体テストと変異とスモークテストです。どれもプログラムが合否を決めます。その結果を読むのも、落ちたときに直すのも、AI です。
CC BY 4.0
検証手順: 判定を 1 つ反転して、失敗になることを確かめる
前提
スモークテストを 1 本用意し、その中に、合否をデータで決める判定(この回の例では、対戦が終わったあと、DB に残ったデータを見る部分)を入れておきます。手元でスモークテストが最後まで通ることも、確かめておきます。
所要時間: 20 分(スモークテストの実行時間を含みます)
手順
- あなたが、データを見ている判定を 1 つ反転します(本文の例なら、出題順の一致を見る行を「不一致なら合格」に書き換えます)。反転するのは判定の行だけです。ブラウザの操作の側は、そのままにします
- あなたが、スモークテストを走らせます
- あなたが、本番で不具合報告が届く経路も一度だけ確かめます。フォームの送信先をわざと壊し、届いた報告の件数を見ます
合格条件(すべて満たすこと)
合格の条件は 3 つです。手順 2 では、結果が失敗になり、ブラウザの操作の側は通って、データの判定の側だけが失敗していることを確かめます(この回の例では playwright=0 verify=1 の形)。手順 3 では、報告が 0 件になることを確かめます。
合格しなかったとき
- ブラウザの操作の側だけが失敗している —— あなたが反転した行とは別の行が、verify の数を決めています
- 手順 3 で報告が届き続ける —— 報告は、あなたが壊した送信先とは別の経路で届いています
この検証手順の中心は、手順 3 です。報告 0 件は「不具合が無い」とも「経路が切れている」とも読むことができ、件数だけでは区別が付きません。一度壊して 0 件になるのを見ておくと、次に 0 件が続いたときに何を疑えばよいかが分かります。
後始末
反転した判定の行を元に戻し、スモークテストをもう一度走らせて、合格に戻ることを確かめます。手順 3 で壊した送信先も元に戻し、報告が届くことを確かめます。
CC BY 4.0 はここまで
次回は「AIの話を聞かない技術」。AI が少しズレたことを言っても、は説明していません。 それでも、AI は正しい向きに戻ります。
連載「AIの意見を聞かない技術」
- ← 前回: 第 5 回 自動テストを書かない技術
- → 次回: 第 7 回 AIの話を聞かない技術
- 全回の一覧: 序論 断ったはずの提案が、また来る