テスト基盤の先行構築:仕様を実行可能な制約に変える
テストは spec の実行可能な形式です。受け入れ基準は自然言語で記述されますが、テストはそれを機械が実行できるアサーションに翻訳します。この翻訳はビジネスコードの最初の一行が書かれる前に完了しなければなりません。そうすることで、Agent は実行の最初のステップから feedback 信号を得られます。このステップは spec に忠実かどうか、と。
しかし、一つの feature は一つの塊ではありません。フロントエンドのページ、バックエンドのインターフェース、データベース操作にまたがり、複数のモジュールに関わります。モジュール間の境界を定義しなければ、テストはエンドツーエンドの全経路しか取れません。フロントエンドの操作からデータベースの書き込みまで一気通貫です。全経路テストには価値がありますが、粒度が粗すぎます。テストが失敗しても、フロントエンドのレンダリングの問題なのか、バックエンドのロジックの問題なのか、データベースクエリの問題なのかがわかりません。原因特定のコストが高く、修正サイクルが長くなります。実行過程で Agent に精確な feedback を与えるためには、テストがモジュールレベルで実行できなければなりません。そのためにはまず、テスト可能なモジュール境界を定義する必要があります。
API Contract:テスト可能な境界
API contract は各モジュールの入力と出力を定義します。バックエンドのインターフェースはどのパラメータを受け取り、どの構造を返し、どの状況でエラーを返し、どのエラーを返すか。この定義があることで、3つのことが可能になります。
第一に、モジュールを独立してテストできます。バックエンドがまだ書かれていない段階で、フロントエンドは contract に基づいて mock server を構築し、自身の呼び出しロジックが正しいかを検証できます。フロントエンドがまだ書かれていない段階で、バックエンドは contract で定義された入力パラメータを使って直接インターフェースをテストできます。各モジュールは自身の境界内で「contract が規定した振る舞いを実装しているか」を検証でき、他のモジュールの完成を待つ必要がありません。
第二に、テストのアサーションに明確な参照対象ができます。contract がこのインターフェースは status と data フィールドを含む JSON を返すと規定していれば、テストは返り値にこの2つのフィールドが含まれていることをアサートします。参照対象は contract ドキュメントであり、Agent がたまたま書いた実装ではありません。
第三に、問題の特定が精確になります。あるモジュールのテストが失敗すれば、問題はそのモジュールの境界内にあります。全経路の中で層ごとに調査する必要がありません。
これは複数の Agent による並行開発の前提でもあります(第5章で詳述)。contract があってこそ、複数の Agent が異なるモジュールを独立して開発し、互いに衝突しないことが可能になります。
境界の定義が完了したら、次のステップはこれらの境界上にテストを構築することです。
コーディング前のテスト生成
テストの入力には2つのソースがあります。spec の受け入れ基準と、アーキテクチャドキュメントの API contract です。テスト作成の時点では、ビジネスコードはまだ存在しません。この時間順序が、テストが実装から独立していることを保証します。テストは spec の意図を直接翻訳したものであり、実装の詳細に汚染されません。
受け入れ基準から生成されるテストは、ユーザーが知覚できる振る舞いをカバーします。spec に「支払い成功後、請求ページにレコードが1件表示される」と書かれていれば、テストは次のようになります。支払いインターフェースを呼び出し、有効なパラメータを渡し、請求テーブルにレコードが1件追加されたことを検証し、インターフェースが成功ステータスコードを返したことを検証する。spec に「各目標に KR は最大5つ」と書かれていれば、テストは次のようになります。目標を1つ作成し、KR を5つ追加し、6つ目の追加を試み、インターフェースがエラーを返すことを検証する。各受け入れ基準は1つ以上のテストケースに対応し、Given/When/Then 構造がテストの setup/action/assertion に自然にマッピングされます。
API contract から生成されるテストは、インターフェースレベルの振る舞いをカバーします。contract は各インターフェースの入力パラメータ、返却構造、エラーコードを定義しており、テストはこれらの約束が実装されているかを一つずつ検証します。このタイプのテストは受け入れ基準テストより粒度が細かく、モジュール境界上の契約準拠度をカバーします。
まだ存在しない依存関係は mock で置き換えます。フロントエンドのテストは mock server でバックエンドのインターフェースをシミュレートし、バックエンドのテストは mock データベースや mock 外部サービスを使います。mock の振る舞いは API contract の定義に基づいており、でたらめに作られたものではありません。これにより、テストフレームワークはコーディング開始前に構築・実行が可能になります(実装がまだ存在しないため全て赤の状態です)。
なぜ統合テストが受け入れの主力であり、単体テストではないのでしょうか。統合テストはユーザーストーリーが通るかどうかを検証し、spec の意図に直接対応します。単体テストは関数の返り値が正しいかを検証しますが、これは実装の詳細であり、spec の意図との距離が遠すぎます。さらに重要な点として、Agent は実装の内部構造を調整することで単体テストを通すことができますが、完全なユーザージャーニー(API 呼び出しからデータベース書き込み、返り値検証まで)を偽装するのははるかに困難です。単体テストが無用なわけではなく、Agent は自身の開発プロセスを補助するために自由に単体テストを書くことができます。しかし受け入れの根拠としては、統合テストの方がより信頼性の高い信号です。
プロセス内の継続的検証
テストフレームワークが整うと、検証は実行プロセスの一部となり、事後のチェックステップではなくなります。
Agent の作業は小さな塊に分割されます。インターフェースを1つ実装する、ユーザーストーリーを1つ完成する、境界条件を1つ処理する。各塊の完了後、すぐに関連するテストを実行します。テストが通れば次の塊に進みます。テストが失敗すればその場で修正し、偏差を抱えたまま先に進みません。
このリズムの価値は、偏差の生存時間を最短に圧縮することにあります。序章で分析した通り、Agent は長いチェーンの実行中に漂流します。第10ステップのコードは第1ステップで参照した spec からすでに逸脱しているかもしれません。ある偏差が第5ステップで発生し、第50ステップでようやく発見された場合、間の45ステップの成果物はすべて偏差のある基盤の上に構築されている可能性があります。偏差が第5ステップでテストに捕捉され修正されれば、影響範囲はこの1ステップ内に限定されます。
この closed-loop 構造は、spec の章のイテレーションループと同じパターンが異なるフェーズに投影されたものです。spec フェーズのループは、人が意図を書く → Agent が展開する → 交差検証で問題が露呈する → 修正する → 再検証する。実行フェーズのループは、spec が振る舞いを定義する → テストが振る舞いをコード化する → Agent が実装する → テストが feedback を返す → 修正する → 次のステップへ。2つのループの共通特徴は、各ステップに Agent の成果物とは独立した feedback 信号があることです。spec フェーズの feedback 信号は交差検証であり、実行フェーズの feedback 信号はテストです。
テストを通じた仕様の検証
テスト基盤の先行構築は、spec の品質に対する最も早い検証です。
ある受け入れ基準がテストに変換できない場合、問題はテストにあるのではなく、spec にあります。「システムは良好なユーザー体験を持つべきである」ではテストが書けません。観測可能な振る舞いが定義されていないからです。「ユーザーがフォームを送信してから2秒以内に確認ページが表示される」であればテストが書けます。具体的な入力(フォーム送信)、出力(確認ページ)、制約(2秒以内)が定義されているからです。
この検証はコーディングの前に行われます。spec に曖昧な箇所があれば、テスト作成の段階で露呈し、spec フェーズに戻って修正できます。コストは非常に低いです。コーディング完了後に spec の曖昧さが発覚した場合、修正のコストにはすでに書かれたコードとテストが含まれ、spec フェーズでの修正よりはるかに高くなります。
テスト基盤は振る舞いの正確性の検証を解決します。spec が述べた機能を、コードは実現しているか。しかし序章ですでに指摘した通り、テストはすべての意図の漂流をカバーするわけではありません。ハードコーディング、スコープ外の変更、アーキテクチャの逸脱は、振る舞いレベルの問題ではなく、テストはそれらを捕捉しません。次のセクションでは、code review によってテストの死角をどう補完するかを説明します。
Harness Engineering Playbook · AgentsZone Community