Coding Agentに実装を任せていると、テストも簡単に増えていきます。
コードを変更する。
AIが変更に合わせてテストを書く。
テストを実行してGreenになる。
便利です。
ただ、最近少し気になることがあります。
そのテスト、本当に必要だったのだろうか。
「変更したからテストを追加する」でいいのか
例えば、あるAPIで次の状態を扱っていたとします。
draft
active
paused
archived
仕様変更によって paused がなくなりました。
変更後は、
draft
active
archived
だけを受け付けます。
この変更をCoding Agentへ任せたところ、次のようなテストも追加されたとします。
it("pausedを受け付けない", () => {
expect(
updateSchema.safeParse({ status: "paused" }).success
).toBe(false);
});
一見すると、特に問題のない回帰テストに見えます。
実際、テストもGreenになります。
ただ、ここで一度考えたい。
paused を受け付けないこと自体が、今後も守り続けたい仕様なのでしょうか。
それとも、
以前は
pausedが存在していて、今回削除した
という変更履歴をテストとして残しているだけでしょうか。
もちろん、
pausedは今後絶対に受け付けてはいけない
こと自体が重要な仕様なら、テストを残す意味があります。
問題なのは、「コードを変更した」という事実だけを理由にテストまで追加してしまうことです。
AIによって、今度はテストが増えすぎる
これまでは、テストを書くコストそのものが問題になることがありました。
既存コードを調べる。
テストを書く。
テストデータを用意する。
壊れたら直す。
AIによって、このコストはかなり下がりました。
これは良いことです。
ただ、安く作れるようになると逆の問題も出てきます。
変更のたびに、
- 削除した値を拒否するテスト
- 削除したAPIを呼べないテスト
- 削除した分岐へ入れないテスト
- 過去の仕様へ戻らないテスト
を追加していけば、テストは確実に増えます。
でも、テストコードもコードです。
増えればレビュー対象も増えるし、CI時間も延びる。仕様変更時に直す場所も増えます。
さらに気になるのは、古い実装の都合をテストが仕様として固定してしまうことです。
テストが多いことと、守るべきものを正しく守れていることは同じではありません。
まず「何を守るテストなのか」を確認する
先ほどの例なら、自分はすぐに paused 専用のテストを追加するのではなく、もう少し確認します。
例えば現在のスキーマがこうなっていたとします。
const statusSchema = z.enum([
"draft",
"active",
"archived",
]);
さらに、現在許可している値と、不正な値を拒否することについて、すでにテストがあるとします。
it.each(["draft", "active", "archived"])(
"%sを受け付ける",
(status) => {
expect(
statusSchema.safeParse(status).success
).toBe(true);
}
);
it("許可されていないstatusを受け付けない", () => {
expect(
statusSchema.safeParse("unknown").success
).toBe(false);
});
ここでは、
- 現在許可している値を受け付ける
- 許可していない値は拒否する
という振る舞いは、すでに確認できています。
その状態で、paused という過去の値だけを名指しして、さらに独立したテストとして残す必要があるのか。
ここは考えた方がいい。
見たいのは、
今回コードを変更したか
ではありません。
この振る舞いを、今後も独立して守る必要があるか
です。
新しいテストを書く前に、既存の保証を見る
ある振る舞いが重要でも、新しいテストが必要とは限りません。
例えば、
- 型で制約されている
- スキーマで入力を制限している
- 既存の単体テストで確認している
- DB制約で防いでいる
- 統合テストで確認済み
- CIの静的解析で検出している
かもしれません。
そこを調べずに「変更したからテストを書く」を続けると、同じことを何層にも重ねて確認するようになります。
もちろん、重要なものを複数の仕組みで守ること自体が悪いわけではありません。
ただ、意図して重ねた保証と、なんとなく増えた重複は違います。
だからAIにも、新しいテストを書く前に既存の保証を見てほしい。
細かなルールを全部AIに書いておけば解決するのか
ここで考えたくなるのが、CLAUDE.md や AGENTS.md のようなファイルへ、
スキーマを変更したら単体テストを書く
Enumを削除したら削除値を拒否するテストを書く
条件分岐を追加したら各分岐のテストを書く
と細かく書く方法です。
短期的には分かりやすいと思います。
ただ、自分はこれを増やし続ける方法には少し懐疑的です。
ルールが「変更したらテストを書く」なら、AIは必要性とは関係なくテストを増やせます。
しかも、そのルール自体も増え、古くなります。
OpenAIが公開したHarness Engineeringの事例でも、大きな AGENTS.md に情報を集めるより、短い入口から必要な資料へ辿れる構成へ移し、機械的に守れる制約はlintや構造テストで検証しています。
自分はテスト方針も近いと思っています。
すべてのテストケースをルールとして列挙するのではなく、どう判断するかを共通化する。
そして、機械的に守れるものはCIやツールへ移す。
「テスト追加なし」も正常な結果にする
AIへ、
必要なテストを追加してください
と頼むと、AIにとって分かりやすい成果物は新しいテストコードです。
何も追加しなければ、仕事をしていないようにも見えます。
なので、求める結果自体を変えた方がいいと思っています。
今回守るべきもの:
現在許可されているstatus以外を受け付けないこと
既存の保証:
スキーマで許可値を制限済み
許可値と不正値を確認する既存テストも存在する
追加テスト:
なし
理由:
pausedという過去の値そのものを
独立した仕様として固定する必要はないため
これも正しい結果として扱う。
つまり、AIにやらせたいのは、
という流れです。
「テストを書いたか」ではなく、
必要な保証を調べ、足りなければ追加したか
を見る。
AIによってテストを書くコストが下がった今だからこそ、ここは重要になると思っています。
ただし、AIに仕様を発明させない
もう一つ注意したいことがあります。
AIが本番コードを読んで、
現在こう動いているので、これが正しい仕様です
と決めてしまうことです。
今のコードが正しいとは限りません。
バグかもしれない。
暫定実装かもしれない。
プロダクト上の意図と食い違っているかもしれない。
判断材料が足りないなら、
現在のコードではこうなっている
既存テストではこうなっている
仕様書には記述がない
→ どちらを正とするか確認が必要
まで整理して止まればいい。
AIに任せたいのは、分からないことを埋めることではありません。
判断に必要な材料を集めることです。
何を今後も守る仕様とするのか。
そこにプロダクトやアーキテクチャ上の判断が必要なら、人間へ戻せばいい。
標準化したいのは、テストを追加するまでの判断
AIによって、実装するコストは下がりました。
テストを書くコストも下がっています。
ただ、書くコストが下がったからといって、考えるコストまでゼロになったわけではありません。
むしろ簡単にコードを増やせるからこそ、
なぜ、このテストが必要なのか
を以前より意識する必要があります。
自分が作りたいのは、
変更する
↓
何を守る必要があるか確認する
↓
既存の保証を調べる
↓
不足している
↓
初めて新しい保証を追加する
という流れです。
テストを書くこと自体を目的にしない。
テストを書かないことも目的にしない。
必要な保証だけを残す。
AIがテストを書ける時代だからこそ、「何をテストしないか」を判断することも品質設計の一部になる。
そしてAIに共通化したいのは、細かなテストケースの一覧ではありません。
テストを追加するまでに、何を確認し、どう判断するか。
自分はそこを仕組みにしていくことの方が、これから重要になると思っています。
関連記事
- AIが実装し、AIがテストし、AIが「問題ありません」と言う時代の品質保証
- AI時代のCode Reviewを再設計する ― Diff ReviewからRisk-based Change Verificationへ
- AI Codingのボトルネックは、もう実装ではない ── 次に必要なのは「検証の設計」