この記事の要点
- AIにテストを書かせると、境界値・異常系・非同期処理・副作用の検証が毎回同じように抜けやすい傾向があります。
- 原因は、AIが正常系の使われ方を推測してテストを組み立てるため、失敗パスや副作用の検証が後回しになりやすいことです。
- 対策として、プロンプトに検証観点を番号付きで明示し、生成後に人間がレビューする工程を挟むことが有効です。
AIにコードを書かせる機会が増えるにつれて、テストコードの生成もセットになってきました。GitHub CopilotやClaude Codeにテストを書かせると、たしかに速いです。ただ、何度か使ってみると、抜ける場所にはっきりしたクセがあることに気づきます。この記事では、AIにテストを書かせるときに毎回抜けやすい観点を整理し、プロンプトとレビューでどう補うかをまとめます。
そもそも「同じ場所が抜ける」とはどういう現象か
AIにテストを書かせると、正常系のテストはすぐに、それなりの粒度で出てきます。関数に値を渡し、期待どおりの戻り値が返るか確認するテストです。ここまでは安定して生成されます。
問題は、その先です。境界値や異常系、非同期処理まわりのテストになると、生成される確率がぐっと下がります。しかも抜け方が毎回似ています。特定の関数だけ抜けるのではなく、特定の種類の観点がまるごと抜け落ちる現象です。
これは実装コードを読むAIの動き方と関係していると考えられます。AIは与えられた関数のシグネチャと実装から、最もありそうな使われ方を推測してテストを組み立てます。失敗するパスは、コードを読むだけでは推測しにくく、後回しにされやすいと考えられます。
AIが見落としやすい5つのテストパターン
実務でAI生成のテストをレビューしていると、抜けるパターンはだいたい以下の5つに集約されます。
1. 境界値と異常系の入力
配列の先頭や末尾、空配列、0件といった境界値のテストは抜けやすい代表例です。上限件数についても同様です。null や undefined、空文字列を渡した場合の挙動も、明示的に頼まない限り出てこないことが多いです。
2. 例外処理とエラーメッセージの中身
例外がスローされること自体のテストはまだ出てきますが、エラーメッセージの文言やエラーコードまで検証するテストは省略されがちです。try-catchの中で握りつぶされる処理も見落とされやすい箇所です。
3. 非同期処理とタイミング
Promiseの競合状態や、タイムアウト、リトライの回数といった時間軸が絡むテストは、AIにとって組み立てにくいようです。非同期処理を含む関数では、成功パスのテストだけで終わっているケースをよく見かけます。
4. 副作用の検証
DBへの書き込み内容や、外部APIを呼んだ回数、渡した引数の中身といった副作用の検証は抜けやすいです。モックは「呼ばれたか」までは検証しても、「何回」「どんな引数で」呼ばれたかまでは踏み込まないことが多いです。この観点は見落とされがちです。
5. 既存コードのバグをそのまま仕様として固定してしまう
これはやや性質が異なりますが、実務上は一番厄介です。実装済みのコードにバグが含まれている場合、AIはそのバグの挙動をそのまま「正解」としてテストを書いてしまうことがあります。テストが通っても、バグそのものは固定化されるだけです。
実際に試してみた所感
手元のプロジェクトで、Claude Codeにいくつかのユーティリティ関数のテストを書かせてみました。正常系のテストは十分な粒度で、体感5分もかからずに出てきました。
一方で、境界値や非同期処理のテストは、何も指示しないと1〜2ケースにとどまることが多かったです。特にPromiseを返す関数では、rejectされた場合のテストが抜けている割合が高いと感じました。
プロンプトで「異常系と境界値も網羅して」と明示すると、テストの本数はかなり増えます。ただ、増えたテストが本当に価値のある観点かどうかは、結局人間が読んで判断する必要がありました。
抜け漏れを減らすプロンプトの工夫
観点を具体的に指定すると、AIの出力は変わります。抽象的に「テストを書いて」と頼むのではなく、観点をリストにして渡すのが有効です。
たとえば、次のような指示が効果的でした。
以下の観点でテストケースを網羅してください。
1. 正常系(代表的な入力)
2. 境界値(0件、1件、上限件数、空配列)
3. 異常系(null、undefined、型が異なる入力)
4. 例外(スローされる条件とエラーメッセージの内容)
5. 副作用(呼び出し回数と引数の内容)
6. 既存実装にバグの疑いがあれば、テストを書く前に指摘する
観点を番号付きで渡すことで、AIが観点を飛ばしにくくなります。特に6番の「バグの疑いがあれば先に指摘する」を入れておくと、既存バグをそのまま仕様化してしまう事故を減らせます。
生成前と生成後で観点のカバー状況を比べると、違いが分かりやすいです。以下は手元での体感を整理した表です。
| 観点 | 指示なし | 観点を明示 |
|---|---|---|
| 正常系 | 生成される | 生成される |
| 境界値 | 抜けやすい | ほぼ生成される |
| 異常系 | 抜けやすい | ほぼ生成される |
| 非同期のタイミング | ほぼ抜ける | 一部生成される |
| 副作用の検証 | ほぼ抜ける | 指示すれば生成される |
非同期のタイミングまわりは、観点を明示してもまだ弱いというのが体感です。ここは人間側でケースを追加する前提で考えたほうがよさそうです。
デメリット・向いていない人・うまくいかないケース
AIにテストを書かせる方法は、誰にでもそのまま向くわけではありません。
まず、生成コードの実装意図を自分で説明できないレベルの初学者には向いていません。AIが書いたテストが妥当かどうかを判断できないと、バグを仕様として固定するリスクに気づけないためです。
次に、複雑な状態遷移を持つシステムでは効果が薄くなります。状態が絡むテストはAIが持つコンテキストの範囲では網羅しきれず、結局人間が設計し直すことになりがちです。
さらに、レビューの手間を惜しむチームには向いていません。観点を指定してもテストの本数は増えるため、無意味なテストや冗長なテストを削る作業が発生します。この作業をしないと、テストコードだけが膨らみ、保守コストが上がります。
テストカバレッジの数値だけを目標にしている現場でも注意が必要です。観点を網羅したように見えても、アサーションの中身が薄いテストが混ざることがあるためです。
よくある質問
AIにテストを書かせると品質は上がりますか
正常系のテストを素早く用意できる点では効率化になります。ただし、異常系や境界値は人間の確認なしでは抜けやすく、品質向上は運用次第です。
境界値のテストはどう指示すれば増えますか
0件、1件、上限件数、空配列、nullのように具体的な値を列挙して渡すと、抜け漏れが減ります。抽象的な指示だけでは境界値は出てきにくいです。
既存コードのバグをテストが固定してしまうのを防ぐには
テストを書く前に、実装内容に疑わしい挙動があれば先に指摘してと依頼するのが有効です。テスト生成と実装レビューを分けて頼む方法もあります。
非同期処理のテストはどう補えばいいですか
Promiseのreject、タイムアウト、リトライ回数といったケースは人間側で追加する前提で考えるのが安全です。AIへの指示だけでは網羅しきれない領域です。
AI生成のテストはそのままマージしていいですか
おすすめしません。観点が網羅されていても、アサーションの中身が薄いテストが混ざるためです。人間によるレビューを一段階挟むのが安全です。
カバレッジの数値は指標として信用できますか
行カバレッジが高くても、境界値や例外の検証が薄いテストでは意味がありません。カバレッジは目安として使い、中身の妥当性は別途確認する必要があります。
どのAIツールでも同じ傾向がありますか
手元で試した範囲ではClaude Codeで同様の傾向が見られましたが、他のツールとの比較は行っていません。ツールごとの違いは今後の検証課題です。
抜けやすい観点をチェックリスト化しておくと、次にAIへテストを頼むときの指示が具体的になります。まずは自分のプロジェクトで、境界値と副作用の検証がどれだけ抜けているか確認するところから始めてみてはどうでしょうか。