AIエージェントにコードを書かせていると、問題は解決しているのに、作りたいものに近づいている感じがしないことがある。

実装が進む。レビューすると問題が見つかる。修正すると、別の問題が見つかる。設計上の懸念が提示され、抽象化を見直し、テストを追加し、そのテストを通すために実装を修正する。個々の作業には理由があり、指摘されている問題も、それなりにもっともらしい。

しかし、しばらく経ってから実際に使おうとすると、最初にやりたかった操作がまだできない。

自分は基本的に、末端の機能から順番に作っていく。小さな処理を実装し、それを組み合わせ、上位の機能を作る。内部のデータ構造や処理の流れから考えるほうが慣れている。一方で、その作り方だと、UI側から見たときに何が足りないのか、どこが使いにくいのかが分かるのは後になりやすい。

では、最初からUIを作ればいいのかというと、それも少し怖い。画面に都合のよいデータ構造を先に決めてしまい、裏側がそれを成立させるための無理を引き受けることになりそうだからだ。

そこで考えたのが、裏側をモックにしてUIを動かしつつ、先にE2Eテストで「作りたいもの」を示しておき、内部は内部で作りながら、両方から擦り寄っていく進め方である。

ただ、考えていくと、本当に欲しいのはUI先行の開発手順ではない気がしてきた。

欲しいのは、AIエージェントが問題を解き続けるだけでなく、今回の開発を完成に向けて収束させるための仕組みなのだと思う。

この記事は、そのためにE2Eテストを使えないか、という仮説を整理するものだ。導入して生産性が何倍になったという実証記事ではない。普段の作り方で感じている違和感から出発し、どういう進め方なら改善できそうか、どこで失敗しそうかまで考えてみたい。

以前の記事では、AI Codingで増える実装をどう検証するかを書いた。今回は、検証を増やすだけでなく、そもそも今回の作業をどこで終えるのかを考える。

部品が増えることと、できることが増えることは違う

末端から作る進め方には、自分にとって明確な利点がある。

例えばファイルを処理するアプリケーションなら、まず入力形式を読み取る処理を作り、変換処理を作り、出力する処理を作る。それぞれの入力と出力を確認しながら進められるし、計算量やメモリの使い方も考えやすい。UIに引っ張られる前に、内部で守りたい条件を整理できる。

この作り方を捨てたいわけではない。問題は、それだけでは利用者の操作が成立したことにならない点にある。

入力形式を読めても、画面からファイルを渡せないかもしれない。変換処理が完成していても、処理中に何が起きているのか分からないかもしれない。出力できても、保存先が分からなければ使う側は困る。

部品ごとの完成と、利用者が目的を達成できることの間には、まだ距離がある。

自分で全部書いている場合、その距離を頭の中で補いながら進めているのだと思う。「これは後でこの画面につなぐ」「ここは最低限動けば次へ進む」といった判断が、明示的な仕様にならないまま作業に混ざっている。

その状態で実装だけをエージェントに渡すと、頭の中に残っていた判断まで伝わったことにはならない。部品を詳しく説明できたとしても、どこまで作れば今回使えるのか、何を後回しにしてよいのかは別の話になる。

ここで必要なのは、内部設計をやめることではなく、内部の進捗とは別に、利用者の側から進捗を測る基準を置くことではないか。

問題を見つける能力だけでは、開発は終わらない

エージェントに「問題がないか確認して」と頼むと、追加で考えるべきことが次々に出てくる。自分の使い方では、それが終わりの見えない作業になりやすい。

ここで、すべての指摘が間違っていると言いたいわけではない。むしろ、正しい指摘であっても、今それを直すべきかは別に判断する必要がある。

「将来、別の保存方式を追加しにくい」という指摘は、将来その予定があるなら重要だろう。「並列実行を増やすと状態管理が複雑になる」という指摘も、並列実行が必要なら考えたい。しかし、今作りたいのが一つのファイルを処理する小さな道具なら、その判断は変わる。

発見された問題を、そのまま現在のタスクへ追加していくと、開発中に終点が動き続けることになる。直した分だけ新しい仕事が追加されるなら、作業量が増えても、残りが減った感じはしない。

逆に、部品のテストが通っただけで「完成」と言われても困る。終わらない問題探しと、早すぎる完成宣言は反対のように見えるが、どちらも「何をもって完成とするか」が実際の操作と結びついていない、という見方ができる。

Anthropicの長時間動作するエージェントに関する報告でも、機能を十分に検証せず完成扱いすることや、単体テストなどは行っていても端から端まで機能していないことが課題として挙げられている。ただし、これは同社の特定の実験についての報告であり、自分が感じている「問題を提示され続ける」現象まで証明するものではない。1

自分の困り方を考えるうえで重要なのは、エージェントの能力をひとまとめに評価することではない。問題の発見、作業の選択、完成の判定を、同じ曖昧な会話の中で処理していることを見直したい。

仮説:E2Eテストを、完成条件の表現として先に置く

そこで、実装前に「何ができたら今回の目標を達成したのか」を、利用者の操作と結果で書いておく。

そのシナリオを自動実行できる形にし、実装の進捗をそこへ結びつける。自分が考えているE2E先行は、そういう使い方である。

通常の回帰テストとして、「今まで動いていたものが壊れていないか」を確認する役割もある。しかし、それより前に「これから何を動くようにするのか」を示す役割を持たせたい。

例えば、「ファイル変換機能を実装する」では、入力、変換、保存、UIのどこまで含むのかが曖昧だ。それを「利用者がファイルを選び、条件を指定し、変換した結果を保存して使える」に変える。

すると、変換関数だけ完成しても、保存できなければ未達だと分かる。一方、その操作が成立し、合意した品質条件も満たしたなら、汎用的なプラグイン機構が未実装でも、今回は完成としてよいかもしれない。

これは、テストがあれば何でも解決するという話ではない。どのテストを、どの範囲の完成判定として使うかを先に合意する、という話である。

もちろん、例から期待する振る舞いを定め、実行可能な仕様として実装を導く考え方自体は新しくない。CucumberのBDDの説明でも、具体例について認識を合わせ、それを自動化可能な形にし、実装へつなげる流れが示されている。2

自分がここで考えたいのは、それをAIエージェントの作業の選択と終了条件に使うことだ。新しい開発手法を発明したというより、既存の考え方を、今困っている場所に適用してみたい。

自分がここで区別したいのは、何を満たせば受け入れるかと、システムのどの範囲を通して検証するかである。この記事では、利用者の主要な受け入れ条件を、実接続のE2Eでも確認する構成を考えている。すべての受け入れ条件をUI操作へ置き換える、という意味ではない。

先に決めるのは、画面の完成形ではない

ここで先に決めたいのは、ボタンの配置やコンポーネントの分割ではない。利用者が何をして、最終的に何を得られるのかである。

以降は説明のために、画像を指定した幅へ縮小して保存する、小さなアプリケーションを仮定する。実際にこの方法で開発して検証した事例ではない。

最初のシナリオは、例えば次のように書ける。

シナリオ:画像を指定した幅へ縮小して保存できる

前提:幅1200、高さ800ピクセルの有効な画像ファイルがある。
操作:利用者が画像を選択し、出力幅に600を指定して処理する。
結果:出力画像を保存できる。
確認:保存した画像は幅600、高さ400ピクセルで、入力に対応する画像内容を持つ。

ここには、DBのテーブル名も、画像バッファの持ち方も、処理スレッド数も出てこない。画面が一枚なのか、ウィザード形式なのかも決めていない。

一方で、「保存できる」だけで十分か、「入力と同じ画像内容を保つ」とは何を確認するのか、といった曖昧さは残る。これらは実装に入る前後で具体化しなければならない。シナリオを書けば仕様が自動的に完成するわけではなく、曖昧な部分を見つけやすくするために書く。

例えば最初の対象をPNGに限定すれば、非可逆圧縮の差異をどう扱うかは後へ送れる。画質の細かい評価より、まず既知の模様を持つ画像で、寸法と内容の対応を確かめる、という切り方も考えられる。

UIテストの操作対象も、DOMの階層や偶然のCSSクラスではなく、利用者が認識するラベルや役割を中心に指定したい。Playwrightも、利用者に見える振る舞いを検証し、内部実装への依存を避ける方針を示している。3

先に固定するのは、実装の形ではなく、今回提供したい価値の輪郭である。

「今回やらないこと」も仕様に含める

完成条件を先に書こうとして、エージェントへ「必要なE2Eテストを全部考えて」と丸投げしたらどうなるだろう。

画像を扱うなら、複数ファイル、ドラッグアンドドロップ、履歴、クラウド同期、再実行、キャンセル、形式変換など、考えられる機能はいくらでもある。それらを全部完成条件へ入れてしまえば、問題リストがテストリストへ変わっただけになる。

だから、今回の範囲は有限にしたい。

先ほどの例なら、最初は単一のPNGを指定し、幅を変更して保存できればよい、と決める。複数ファイルの一括処理や履歴管理は対象外にする。これはアプリケーションが将来持つ機能を否定しているのではなく、今回の開発で何を完成させるかを区切っている。

この区切りは、機能だけでなく、扱う入力や利用環境にも必要だろう。「どんな画像でも動く」ではなく、まずどの形式、どの規模を対象とするかを示す。ただし、対象外の入力に対して壊れてよいという意味ではない。受け付けないなら、拒否する振る舞いを決める必要がある。

そして、実装中に新しい要求が見つかったら、発見したこと自体は記録する。ただし、自動的に今回の範囲へ追加しない。今の目的に不可欠なのか、次の段階でよいのか、別途判断する。

自分がE2Eで欲しいのは、無限に細かい仕様書ではない。「これが動けば、とりあえず自分が使える」という最小限の到達点を、会話から取り出して残すことだ。

先に決める範囲が大きすぎるなら、まだ最初の一歩を決められていないのかもしれない。テストを書く前に、作りたいものを一度小さくする必要がある。

UIと内部は、「操作と結果」で合流させる

最初の懸念へ戻る。UI側から作ると、内部のデータ構造が画面の都合に引っ張られないだろうか。

この点は、先に何を合意するかで変わると思う。UIが使うデータの形を、そのまま内部の保存形式や処理形式として採用するなら、確かに怖い。しかし、利用者が要求する操作と、その結果として観測できる状態を合意するだけなら、内部にはまだ選択の余地がある。

例えば、処理の開始、状態の取得、結果の取得という境界を置く。概念的には、次の程度でよい。

startResize(input, options) → jobId
getJob(jobId) → 待機中/処理中/成功/失敗
getResult(jobId) → 出力画像

これはAPI設計を確定した例ではない。実際には入力の渡し方や失敗の表現なども必要だし、同期処理で十分ならジョブという概念すら不要かもしれない。両側が話し合う場所を、まず小さく作るという意味である。

UI側は、その境界のモックを使って操作を組み立てる。内部側は、バッファ、処理の分割、保存方法などを考える。UIが一覧表示用のデータを欲しがっても、内部がその一覧と同じ構造でデータを持つ必要はない。必要なところで変換すればよい。

アプリケーションの内部と外部の接続を分離し、テスト用の実装などへ差し替えられるようにする考え方は、Ports & Adaptersでも説明されている。ただし、今回はその全体を厳密に導入したいのではなく、小さな境界を設ける考え方を借りたい。4

ここで重要なのは、境界も変更可能だということだ。UIからの要求だけで決めず、内部の制約も返す。「正確な進捗率は出せない」と分かったなら、不正確な数字を無理に作るのではなく、UI側の表現を変える。

両側から擦り寄るとは、間に決めた仕様へ双方を無理やり押し込むことではない。双方で分かったことを使い、合流地点も修正することだと思う。

モックは、都合のよい世界を作りすぎない

モックを用意するとき、すべての操作が即座に成功するように作れば、画面は簡単に動く。しかし、その画面を実装上の約束だと思い始めると危ない。

例えば、処理には時間がかかるのに、モックでは一瞬で終わる。そうすると、処理中の状態を考えないままUIが完成してしまう。失敗しないモックなら、失敗後に入力を修正してやり直す操作も、後から付け足すことになる。

そこで、画面の成立に影響する状態は、早い段階でモックに持たせたい。成功した結果だけでなく、処理中、入力が不正な場合、処理が失敗した場合などを、意図して切り替えられるようにする。

ただし、あらゆる異常を最初から再現する必要はない。今回の操作にとって、何を表示し、何を操作可能にするかが変わる状態を優先する。テストでは、その状態を明示的に選べるようにし、偶然の待ち時間やランダムな失敗に依存させない。

また、モックを作る前に、内部側でも短い検討はしておきたい。扱う画像を全部メモリへ載せるのか、処理中に中断できるのか、出力はいつ利用可能になるのか。詳しい実装はまだなくても、UIの前提を壊しそうな条件は洗い出せる。

逆に、モックへ本物と同じ処理エンジンを実装し始めたら、本末転倒である。仮実装を維持するために、もう一つのアプリケーションを作ることになる。

モックの役割は、内部の完成を装うことではない。利用者側の要求や状態の抜けを、内部の完成前に発見することに絞りたい。

モックで通ったテストを、全体の完成判定には使わない

ここは明確に分けておく必要がある。

ブラウザを操作しているからといって、そのテストがアプリケーション全体を検証しているとは限らない。PlaywrightのAPIモックの例でも、実APIにはリクエストを送らず、差し替えた応答を画面で確認することができる。5

つまり、UIとモックを使ったテストが通っても、分かるのは、その想定した応答に対してUIが動いたということまでだ。画像が本当に変換できることは、まだ確かめていない。

自分なら、少なくとも次の区別をつける。

検証する範囲確認したいこと
UI+モック想定した状態で、表示と操作が成立するか
接続部分の契約UI側の要求・応答の期待が、実際の実装と一致するか
内部の単体・機能テスト処理や状態遷移、保存結果が正しいか
実接続のE2E利用者の操作が、本当の処理結果までつながるか

APIを挟む構成なら、Pactのように、利用側で期待する具体的な要求と応答を提供側でも検証する仕組みが参考になる。型定義の共有だけで満足せず、実際の要求と応答の組み合わせを確かめたい。6

ただし、契約が一致しても、処理結果まで正しいとは限らない。Pactの説明でも、メッセージの整合性を確かめる契約テストと、実際にデータが保存されたかなどを確かめる機能テストは区別されている。7

同一プロセス内の小さなアプリケーションであれば、大きな契約テスト基盤まで作らなくてもよいだろう。共有した振る舞いのテストを両実装へ適用するなど、境界に応じた軽い方法から考えたい。

名前よりも、「何を通して、何を確認したのか」を取り違えないことが重要だ。UIの試作が成立したことと、今回作りたい機能が成立したことは、別々に記録する。

「完了しました」ではなく、できあがったものを見る

E2Eを先に置くとしても、何を検証するかが弱ければ、完成条件としての役割を果たさない。

画像の変換ボタンを押して、「完了しました」と表示されたら成功とする。このテストでは、変換処理を行わずに文字を表示するだけでも条件を満たせる。悪意のある実装を想定しなくても、配線の抜けた画面を合格にしてしまう。

だから、最終的な成果物へ確認を届かせたい。

画像の例なら、実際に取得した出力ファイルを、アプリケーションの表示とは別に読み取る。寸法が期待どおりか、内容が入力に対応しているかを確認する。必要なら入力ファイルが勝手に変更されていないことも確かめる。

ここで、期待する出力を本番と同じ変換関数で作り、それと比較するだけでは、その関数が間違っている場合に気づけない。既知の模様を使う、手で確かめられる小さな入力を使うなど、実装と同じ思い込みだけに依存しない確認を考えたい。

保存の意味にも注意がいる。画面を再読み込みしてデータが残っていても、それだけで永続化を証明したことにはならない。サーバーのメモリに残っているだけかもしれない。プロセス再起動後も残ることが要求なら、その条件を含めて確認する。

反対に、利用者へファイルを渡せれば目的を達成するアプリケーションに、必要のない履歴DBを追加する理由もない。どこまで結果を残す必要があるかは、作りたいものから決める。

また、実接続の対象範囲は明記したい。自分の変換処理と保存処理は本物を通す一方、制御できない外部サービスだけ代替にする、といった構成は考えられる。その場合は、その外部連携まで検証済みだとは扱わない。

テストの緑色ではなく、緑色にするために実際に何を観測したのかを見たいのである。

両側を完成させてからつなぐのでは遅い

「UI側と内部側を別々に作る」と書くと、二つを並行して完成させ、最後に結合する方法にも見える。しかし、自分が考えているのは、それとは違う。

最初の一機能について、できるだけ早く実接続する。

画像の例なら、まず一種類の小さな画像を、固定された縮小条件で処理して保存できればよい。この時点では、対応形式が少なくても、設定画面が簡素でもよい。ただし、その一つの操作は、モックではなく実際の処理結果まで通るようにする。

その後、幅の指定を自由にする。入力エラーを扱う。処理中の表示を整える。新しい部分ではまたモックを使って探索してもよいが、すでにつながっている経路を壊さないように進める。

『Growing Object-Oriented Software, Guided by Tests』には、最初にWalking Skeletonをテストすることや、各機能を受け入れテストから始めることが、開発の流れとして位置づけられている。自分の案でも、初期に小さな実接続を作る部分は、この考え方に近い。8

ここで大切なのは、内部の最適化や共通化を全部終えてから接続しようとしないことだ。接続して初めて、内部が返している状態ではUIが判断できないことや、画面の操作順では必要な情報が揃わないことが分かるかもしれない。

それは、どちらかの設計が悪かったというより、接続したことで初めて得られた情報である。修正が小さく済むうちに、その情報を得たい。

両側から擦り寄るなら、最後に一度だけ握手するのではなく、小さな機能ごとに何度も接続を確かめる。ここを外すと、モックが両者の食い違いを長期間隠す仕組みになってしまう。

テストを通すために、ゴールを変えられては困る

エージェントにテストを書かせ、そのエージェントに実装を任せる場合、もう一つ考えたい問題がある。実装がテストに合わないとき、テストのほうを変更できてしまうことだ。

例えば、保存された画像を検証するテストが落ちているので、確認を「保存ボタンが表示される」に変える。失敗するシナリオをスキップする。実バックエンドが動かないので、その部分だけモックへ戻す。これらはテスト結果を緑色にできても、最初に作りたかったものができたことにはならない。

だから、利用者視点の受け入れ条件と、実装上の都合で変えてよい部分を分けたい。

ボタンのラベルが変わったため操作箇所を修正することと、「出力画像の内容は確認しない」と条件を弱めることは違う。前者でも変更内容は確認したいが、後者には目的そのものを変える判断が含まれる。

Anthropicの前述の報告でも、機能一覧を用意し、一度に一つの機能へ取り組ませ、テストの削除や変更を制限する構成が紹介されている。自分の案でも参考になるが、「包括的な機能一覧」をそのまま増やすのではなく、今回の対象を人間が選ぶ点を重視したい。1

ただし、プロンプトに「変更禁止」と書くだけで確実に守られる、と考えるつもりはない。受け入れ条件の変更差分を別扱いにする、評価用のテストを実装とは別の権限で管理するなど、必要な強さに応じた仕組みを考える。

同時に、テストが間違っている可能性も認めなければならない。実現できない要求だったり、想定していた仕様が不要だったりすることはあり得る。そのときは、理由を示して条件を改訂すればよい。

固定したいのは、永久に変更できない仕様ではない。実装上の都合だけで、完成の意味が黙って変わらないことである。

問題を発見することと、今直すことを分ける

E2Eを置いたとしても、「見つかった問題は全部直す」という作業ルールを残したら、開発が終わらない状況はあまり変わらない。

そこで、問題が提示されたら、まず今回の目標との関係を明らかにしたい。

一つは、受け入れシナリオを成立させるために必要な問題だ。出力画像の寸法が違う、処理が終了しても取得できない、失敗後に操作できなくなる。こうした問題は、今回の作業として直す理由が説明しやすい。

もう一つは、機能とは別に守ると決めた品質条件に関わる問題だ。入力ファイルを意図せず壊さない、アクセスできないはずのデータを返さない、秘密情報をログへ出さないなどである。これらは、通常の操作が成功していても見逃してよいとは考えない。

それ以外に、将来の機能追加へ備える改善や、今の規模では実害の分からない最適化がある。これらは価値がないのではなく、今回実施する理由がまだない可能性がある。

この違いを、指摘の語調ではなく、根拠で判断したい。「重大な設計上の問題です」と書かれているだけではなく、どの条件で、何が壊れ、今回の利用にどう影響するのかを示してもらう。

もちろん、最初に決めた条件に含まれていない深刻な問題が見つかることもある。その場合は、テストに書いていないから無視するのではなく、いったん完了判断を止め、範囲を見直す。具体的な根拠がある危険と、際限なく想定できる改善余地を同じ扱いにしない、ということだ。

そして、新しい問題を今回へ追加するなら、何が増えたかを明示する。必要なら他の作業を後へ送る。「範囲は変わっていないのに、残りだけ増え続ける」という状態を避けたい。

問題を発見したエージェントが、そのまま優先順位も決める必要はない。発見する役割と、今回の作業へ入れる判断を切り離す。それだけでも、開発の見通しは変わるのではないかと思う。

エージェントへ渡す作業単位を変える

実際に試すなら、エージェントへの依頼も「このモジュールを改善して」から、もう少し具体的な単位へ変えたい。

例えば、「画像の縮小と保存というシナリオを、実際の実装で成立させる。そのために現在どこが未達なのか確認し、必要な部分を実装する」と依頼する。

作業の出発点は、コードを広く眺めて問題を列挙することではなく、対象シナリオを実行し、どこまで動いているかを確かめることになる。まだ実行環境がなければ、まずその操作を試せる最小限の環境を作る。

実装そのものは、今までどおり小さな部品から進めて構わない。出力寸法を計算する関数が必要なら、その単体テストから書く。画像の読み込みが未実装なら、そこへ取り組む。ただし、その部品が今回のシナリオとどうつながるかを説明できる状態にする。

自分なら、作業ルールは次のようなものから始める。

今回の対象:画像を指定幅へ縮小し、結果を保存できること。

作業前に、対象シナリオの現在の成否と失敗箇所を確認する。
UI用モックでの合格と、実接続での合格を区別する。

対象の成立と、合意した品質条件に必要な変更を行う。
必要な単体テストや回帰テストは追加してよい。
無関係な改善案は記録するが、自動的に実装範囲へ追加しない。

受け入れ条件の削除・緩和、対象範囲の変更は、理由を示して判断を求める。
深刻な品質上の問題を発見した場合も、根拠と影響を報告する。

作業後に、実行した検証、その対象範囲、結果、残課題を報告する。
未実行、スキップ、環境不備を合格として扱わない。
対象シナリオと既存の回帰テスト、品質条件を満たしたら終了する。

この指示を渡すだけで理想どおりに動く、とまでは考えていない。実際には、実行ログと変更差分を確認し、ルールが機能しているかを見直す必要があるだろう。

また、不確かな技術が必要な場合、すぐにシナリオを通す実装へ入れないこともある。その場合は、「この規模の画像を対象メモリ内で処理できるか」といった限定した問いを調べる作業に分ける。

その調査も、「もっと調べるべきことが見つかった」で終わらせず、何を試して、何が分かり、次に実装できるのか、要求を変える必要があるのかを残したい。探索を禁止するのではなく、探索から戻ってくる先を決める。

進捗報告を、修正件数から利用可能な操作へ変える

エージェントから「十五件の問題を修正しました」と報告されても、その数字だけでは完成にどれだけ近づいたのか分からない。

小さな命名修正が十五件なのか、処理全体が動くようになった十五件なのかで意味は違う。テストが百件増えたとしても、作りたい操作をまだ一度も実行していないかもしれない。

だから、進捗もシナリオに結びつけて報告してもらう。

画像の縮小と保存:実接続で合格。
入力画像の選択と出力画像の取得を含めて確認済み。

入力エラー後の再実行:UI+モックでは確認済み。
実際の実装では、エラー後に処理状態が戻らないため未達。

今回扱わない改善:複数ファイル向けのキューの共通化。

これは報告形式の例であり、実際の検証結果ではない。ただ、こう書かれていれば、何が使えるようになり、何がまだ使えないのかは読み取れる。

テストが落ちている理由も分けたい。未実装なのか、回帰不具合なのか、テスト環境が起動しないのか、期待する結果が曖昧なのか。それぞれ次に必要な作業は違う。

未実装のシナリオは目標として未達のまま残し、既存機能の回帰テストとは区別して管理する。ただし、今回完了を宣言する対象については、スキップや未実行を合格へ読み替えない。

なお、シナリオが三つ中二つ通ったから完成度六十七パーセント、といった数字にはあまり意味を持たせたくない。残り一つが全体の難所かもしれないからだ。欲しいのは見栄えのよい進捗率ではなく、今できることと、まだできないことの説明である。

E2Eですべてを検証するつもりはない

ここまでE2Eを中心に書いてきたが、すべての問題をE2Eで検証したいわけではない。

画像の寸法計算に百通りの境界条件があるとして、それを全部ブラウザ操作で確かめる必要はないだろう。計算関数に対するテストで詳しく確認し、E2Eでは代表的な入力について、本当に画面から処理結果まで届くことを確認すればよい。

内部のデータ構造についても同じだ。E2Eで到達できる入力だけから、あらゆる不変条件を確かめようとすると、目的がずれてくる。内部で守るべき条件は、内部のテストで明確にしたい。

性能や安全性も、通常の操作が一度成功しただけでは分からない。対象にするデータ規模、利用環境、待てる時間などを決め、必要な検証を別に用意する。そこまでをすべて最初のE2Eへ詰め込むのではなく、完成条件に加える品質条件として整理する。

また、自動化した操作が時々失敗するなら、その不安定さ自体が収束を妨げる。処理の完了を固定秒数の待機で決めつけず、観測できる状態を待つ。テストごとのデータを分離する。Playwrightの推奨事項にも、テストの分離や、条件が成立するまで待つアサーションが含まれている。3

それでも、E2Eが通ることと、使って気持ちよいことは同じではない。操作の分かりにくさや表示の違和感は、自分でも実際に触って確かめたい。自動化する条件に入っていなかった価値を見つけたら、次の要求として考える。

自分の仮説では、E2Eは品質保証を一手に引き受けるものではない。内部のテスト、品質上の確認、人間による操作確認を残したうえで、作業の向かう先を利用者の目的へ結びつける役割を持つ。

この切り分けをしないと、今度はE2Eテストの維持が開発の主目的になってしまう。

先にテストを書くことにも費用はかかる

この方法で必ず速くなるとは、まだ言えない。

最初にシナリオを整理し、モックを作り、テスト環境を起動できるようにする。その作業にも時間はかかる。画面の使い方が変われば、操作テストを直す必要がある。内部の制約が分かって境界を変えれば、モックと実際の実装の両方を更新することになる。

特に、何を作りたいのか自体がまだ分からない段階では、完成条件を固めることが早すぎる可能性がある。触ってみるためだけの試作なら、捨てる前提で画面を作り、そこから要求を見つけるほうが合う場合もあるだろう。

だから、探索用の試作と、今回の完成へ向かう実装を区別したい。試作で発見した操作のうち、残したいものを受け入れシナリオにする。最初の思いつきをそのまま永久的な契約へ変える必要はない。

また、どんな対象でもブラウザを動かすべきだとは思わない。コマンドラインツールなら、実際のコマンド実行から出力までが利用者の経路になる。ライブラリなら、公開された呼び出し方と結果を確かめるテストが、今回の話に近い役割を持つはずだ。

重要なのは、E2Eという名前のツールを導入することではなく、利用者が目的を達成する経路を、開発の終点として示せるかどうかである。

逆に言えば、その経路が十分短く、今の進め方でも完成条件が共有できているなら、新しい仕組みを足す必要はないかもしれない。自分が試したいのは、内部の実装と改善が続く一方で、使えるところまで届かないと感じる場面だ。

この仮説を、どうやって確かめるか

仮説として書く以上、何が起きたら役に立ったと判断するのかも考えておきたい。

まず見たいのは、最初に実際の操作が成立するまでの時間である。UIのモックが表示できた時間ではなく、入力から本当の出力までがつながった時間を測る。

次に、決めた範囲が完成するまでの総作業量を見る。シナリオを書く時間、テスト基盤を整える時間、モックを維持する時間も含める。実装部分だけが速くなっていても、それ以上に準備が増えていれば、全体として得をしたとは限らない。

エージェントへ人間が介入した回数や、その内容も記録したい。「今はそれをやらなくていい」と軌道修正した回数が減るなら、自分の困り方には効いていそうだ。ただし、判断を放置して介入が減っただけなら改善ではないので、完成物の確認は別に必要になる。

比較するなら、普段の部品中心の進め方、文章で受け入れ条件と対象外を明示する進め方、それに実行可能なテストとモック探索を加える進め方を分けて考えたい。

文章で終点を決めるだけでも大部分が改善するなら、効いたのはE2Eそのものより、範囲と終了条件を明確にしたことかもしれない。そこから自動実行を加えたときに、完成判定の誤りや回帰の見逃しが減るなら、テスト化した意味を説明しやすくなる。

ただし、モック探索とテストの自動化を同時に足した比較では、その二つの効果を分離できない。必要なら条件をさらに分ける。比較対象を増やすこと自体にも費用がかかるので、最初は大きな優位性を証明するより、どこで役に立ち、どこで負担になるかを記録するところからでよいと思う。

比較時には、完成物を判定する条件を共通にしておきたい。モデルや実行環境、使えるツール、作業予算が違えば、進め方以外の要因も混ざる。同じ課題を繰り返す場合には、前の実装を知っている影響にも注意が必要だ。

また、問題の指摘件数が減ったことだけを成功としない。大事な問題を見なくなっただけかもしれないからだ。完成後に見つかった不具合、受け入れ条件を弱めた回数、根拠なく完了扱いした回数も見たい。

期待しているのは、問題が見つからなくなることではない。必要な問題は解決し、不要な寄り道は減り、最初に作りたかった操作が早く確かめられることだ。

もし、テストの維持ばかり増え、実接続は相変わらず遅く、終点も動き続けるなら、この組み合わせは期待どおりに働いていない。その結果も、次の進め方を考える材料になる。

人間が決める場所を、実装から目的の側へ寄せる

自分は、AIに任せず全部手で書く方向へ戻りたいわけではない。使える技術は使いたいし、機械的に確認できることは、できるだけ機械的に確認できる形にしたい。

ただし、実装を任せることと、何を作るのかまで成り行きで決まることは違う。

テストコードの下書きはエージェントに作らせてもよい。しかし、そのシナリオが本当に自分の欲しいものを表しているか、今回そこまで必要なのか、何を結果として確認すべきかは、自分で判断したい。

内部の変更についても、すべての行を常に同じ密度で見るというより、壊れてはいけない条件、接続の境界、実際の検証結果を重点的に確認する仕組みを作りたい。もちろん、それでコードレビューが不要になるとは考えない。扱うリスクに応じて、実装を詳しく見る場所は残る。

E2Eを先に置くことで、人間の仕事がなくなるのではない。実装の細部を追いかけ続けるだけでなく、目的と検証方法を決める側へ、判断を寄せられるのではないかと期待している。

エージェントには、そこへ到達するための実装や調査を進めてもらう。問題を見つけてもらうことも続ける。ただし、見つかった問題を全部解くことと、今回の成果物を完成させることは分ける。

その分担が機能すれば、「コードは増えているのに、何ができるようになったのか分からない」という感覚を、少し減らせるかもしれない。

改善の余地があっても、今回は完成したと言えるようにする

末端の機能から作る進め方を、やめる必要はないと思っている。

その外側に、利用者の操作から要求を発見し、実際の結果まで確かめる流れを足したい。UIはモックで探索し、内部はデータ構造や処理の都合を考えて育てる。そして、小さな機能ごとに実接続し、両側から得た情報で設計を修正する。

そのときE2Eテストは、完成後に不具合を探す道具であると同時に、実装前に「ここへ到達したい」と示すための道具になる。

もちろん、テストは作りたいもののすべてを表せない。品質の確認も、探索も、人間の判断も残る。だから、テストが緑になったら無条件に完成、とはしない。

それでも、合意した範囲と品質条件を満たし、実際に使いたかった操作ができたなら、残っている改善案を認識したうえで、今回は完成したと言ってよいはずだ。

まだ改善できることと、まだ完成していないことは、同じではない。

AIエージェントに、延々と問題を解いてもらうのではなく、作りたいものへ近づいてもらう。そのために、先にテストで「今回は何を作るのか」を置いておく。

自分が試してみたいのは、そういうE2E先行の開発である。


参考資料

以下の資料は、関連する既存手法やツールの検証範囲を確認するために参照した。本記事の開発手法の効果を実証するものではない。参照日:2026年9月28日。