---
title: "「保存した」は検証ではない"
description: "エージェントに作らせた画面の見た目を、数値ゲート・オフスクリーンのPNG・実際の導線の三段で確認しています。撮影までは自動化できても、撮った絵を見て判断する行為だけは自動化できていません。"
category: "AI"
tags: ["AI Agent","Code Review","Testing"]
publishedAt: "2026-08-24"
lastmod: "2026-08-24"
---

画面を持つアプリケーションの見た目をエージェントに作らせるとき、ビルドが通ってテストが全部通っても壊れたものが残るので、数値ゲート・オフスクリーンのPNG・実際の導線という三段で確認しています。撮影までは自動化できましたが、撮った絵を見て正しいか判断する行為だけは自動化できていません。

## テストが通っても壊れているものが残る

すり抜けたものを並べると、種類が見えてきます。

「値が変わった」は「正しくなった」を意味しません。変換の前後で値が違うことを確認するテストは、結果が破綻していても通ります。差がゼロでないことしか証明していないためです。

エラーも警告も出ないまま、古い絵が出たままになることがあります。内部の状態は正しく変わっていて、値を取り出せば期待どおりに返る。それでも画面だけが変わらない、という壊れ方です。数値をどれだけ細かく調べても、壊れているのが数値より先なら捕まりません。

1つずつの値が正しくても、組み合わせた結果が壊れます。2つの角度がそれぞれ84度と89度で、数値としてはどちらも妥当だったことがあります。ところが2つを繋いだら、見た目は何も曲がっていませんでした。

そして、8回のレビューと1019件のテストをすべて通り抜けて、そのままリリースされた欠陥があります。誰もレンダリング結果を見ていませんでした。レビュアーには画面を開かないよう指示が出ていて、指示した側も見ていなかったためです。コードとデータを読んでいる限り、描画の欠陥は原理的に見つかりません。

## 三段に分ける

| 段 | 何をするか | 前の段の盲点 |
|---|---|---|
| 1 | 数値ゲート | ここだけでは、数値としては妥当で見た目が崩れているものが通る |
| 2 | オフスクリーンでPNGを書き出す | 単体で組んだ画面は、実際に出る絵と違うことがある |
| 3 | 実際の導線を起動して撮る | 画面に写らない挙動は撮れない |

段が3つあるのは、前の段が実際に見落としたからです。どれかを飛ばした回は、その段が拾うはずだった壊れ方がそのまま通っています。

第1段で効いたのは、大きさの検査と向きの検査を必ず対にするというルールでした。大きさだけを見るゲートは「90度の曲がりがある」を通しますが、その曲がりが意図した向きかどうかは分かりません。この対にする考え方は他の分野でも同じように使えます。経路の生成なら「繋がっているか」と「どこへ向かって繋がっているか」、シリアライズなら「書き出せたか」と「読み戻して同じものになるか」。大きさだけ、あるいは有無だけを見る検査は、どこでも同じ抜け方をします。

第2段には罠があります。オフスクリーンで描画するとき、それらしい名前のフラグを付けると描画そのものが無効になり、真っ黒なPNGが書き出されたうえで終了コード0が返り、「保存しました」と表示されます。クラッシュより厄介です。成功に見えるためです。

第3段が要る理由は、単体で組み立てた画面が、実際に出る絵と違ったことが2回あったからです。一方は展開されていないプレースホルダーをそのまま描いていて、もう一方は指定に関係なく常に同じ画面を撮っていました。1か月生き延びた欠陥は、実際の導線を通したときにしか再現しませんでした。

## ゲートは事故のたびにしか増やさない

検査項目は、実際にすり抜けた事故が1件起きるごとに1つ足すという決め方にしています。起こりそうな失敗を想像して先回りで並べると、一度も当たらない検査ばかりが増えて、落ちたときの意味が薄れます。

結果として項目数は事故のたびに増えました。ある対象では84→104→110、別の対象ではレイアウトの検査ルールが19個あり、1ルールが、過去に1度リリースまで通ってしまった欠陥に対応しています。

撮る組み合わせも同じ理屈で増えています。変えた箇所だけを撮っていると、レイアウトの崩れはいつまでも見つかりません。中身が最小のとき、最大のとき、空のとき、ウィンドウが最小のとき、言語を切り替えたとき。9つの画面にこれらを掛け合わせて、撮る組み合わせは64から74まで増えました。増やしたきっかけは、中身が最小のときを撮っていなかったために、画面下の300〜430pxの空白と、背景の抜けた帯を見逃した事故です。どちらも「変えた箇所は正しい」PNGしか見ていなかったので通っていました。

## 「保存した」で終わらせた回は全部すり抜けた

撮影は引数で角度も時刻も指定できるところまで自動化しました。それでも、書き出したPNGを実際に開いて見ない限り、検証したことになりません。

ここが自動化の境界です。スクリプトが終了コード0で終わり、ファイルを1枚書いたとしても、それだけでは何も確かめていません。手元の記録では、この工程を「保存した」で終わらせた回はすべて、その回の破綻をそのまま通しています。

![数値ゲート、オフスクリーンでのPNG書き出し、実際の導線を起動しての撮影までは、すべて自動で走る。撮った画像を開いて正しい姿かどうかを判断する工程だけが自動化できておらず、そこから先は人が担う。境界は三段のあとにあるのではなく、撮影と判断のあいだにある](/images/posts/visual-verification-three-gates/automation-boundary.svg)

同じことは、この技術ブログの図版でも起きています。エージェントに書かせたSVGは、ソースを読むかぎり座標も属性も正しく見えて、ラスタライズして初めて矢頭の消失や文字のはみ出しが分かります。対象が違うだけで、境界は同じ場所にあります。

::card[/posts/claude-code-svg-figure-sips-loop]

サブエージェントに任せた場合も同じです。「目で見て確認しました」という報告は、書かれている画像のパスを親が自分で開くまで受け付けないことにしています。報告と実際の画像が食い違ったことが2回あったためです。

## 1つだけ自動化しなかった

見る観点は6つあります。背景の連続性、境界の明示、余白の意図、はみ出し、重なり、整列。このうち5つは数値で測れる形にして、自動で判定するようにしました。

余白の意図だけは、意図的に人間に残しています。規約にもそう書いてあります。「この観点を自動で判定する検査は無い。人が絵を見て判断する」と。

一度は余白を測るルールを実装しました。ただしそのルールは削除されています。余白が広いこと自体は欠陥ではなく、そこに意図があるかどうかが問題で、意図は測れないためです。

この件には後日談があります。規約に「6観点すべてを数値で見るようにした」と書いていた時期があり、それを事実に合わせて直したコミットが残っています。自動で確かめられる範囲を、書いた本人が実際より広く見積もっていました。

## 検証できないものは作らない、という判断

自動で確かめられる範囲は、何を作るかの判断そのものを左右します。

ある機能は「目視でしか確認できない」という理由で、2回にわたって実装を見送られています。効果が正しく出ているかを確かめる手段が目視しかなく、その状態で入れると壊れても気づけないためです。

3回目に採用されました。変わったのは機能の側ではありません。レンダリングする前に数値で判定できる形が用意できたので、「目視でしか確認できない」という反対理由が成立しなくなったからです。数値で先に確かめ、描画がそれを追認する順序になりました。

確かめられる範囲が伸びた途端に、それまで通らなかった設計が通るようになる。この順番は覚えておくといいです。

## 残っているところ

撮影の自動化が進んだぶん、確認のたびに画像を読み込む量が増えました。1つの変更で断面を何枚も撮ると、それを全部開くのは人間の側の作業として残ります。

もう1つ、この運用は「見れば分かる崩れ」にしか効きません。図や画面に書かれている内容そのものが間違っている場合は、何枚撮っても見つかりません。そちらは記載されている値や識別子を1つずつ根拠に当てる別の工程で、目視のループとは分けて回しています。