この記事は、連載「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. あなたが、データを見ている判定を 1 つ反転します(本文の例なら、出題順の一致を見る行を「不一致なら合格」に書き換えます)。反転するのは判定の行だけです。ブラウザの操作の側は、そのままにします
  2. あなたが、スモークテストを走らせます
  3. あなたが、本番で不具合報告が届く経路も一度だけ確かめます。フォームの送信先をわざと壊し、届いた報告の件数を見ます

合格条件(すべて満たすこと)

合格の条件は 3 つです。手順 2 では、結果が失敗になり、ブラウザの操作の側は通って、データの判定の側だけが失敗していることを確かめます(この回の例では playwright=0 verify=1 の形)。手順 3 では、報告が 0 件になることを確かめます。

合格しなかったとき

  • ブラウザの操作の側だけが失敗している —— あなたが反転した行とは別の行が、verify の数を決めています
  • 手順 3 で報告が届き続ける —— 報告は、あなたが壊した送信先とは別の経路で届いています

この検証手順の中心は、手順 3 です。報告 0 件は「不具合が無い」とも「経路が切れている」とも読むことができ、件数だけでは区別が付きません。一度壊して 0 件になるのを見ておくと、次に 0 件が続いたときに何を疑えばよいかが分かります。

後始末

反転した判定の行を元に戻し、スモークテストをもう一度走らせて、合格に戻ることを確かめます。手順 3 で壊した送信先も元に戻し、報告が届くことを確かめます。


CC BY 4.0 はここまで

次回は「AIの話を聞かない技術」。AI が少しズレたことを言っても、私は説明していません。 それでも、AI は正しい向きに戻ります。

連載「AIの意見を聞かない技術」