AIが書いたコードのテスト:いつも緑のテストを避ける
AIはコードと、そのテストの両方を書くので、テストは書いたばかりのコードをただ確認するだけになりがちです。この記事は、緑のテストでは足りない理由を説明し、50万の境界の例を示し、テストの価値を保つ7つの方法を挙げます:仕様から書く、先に赤を見る、自分でコードを壊す、ミューテーションテスト、プロパティベーステスト、境界値の表、実際に動かす。
AIが関わるコードを書く人、レビューする人のための記事で、初心者も対象です。技術用語には短い説明を付けます。
コードの例とその数字は、イメージしやすいように作り直したものです。「カバレッジ100%、ミューテーションスコア4%」は、論文の要旨から取りました(全文は読んでいません)。名前を挙げたツールは例にすぎず、動かしていません。方法は私の提案です。
先に要点#
- AIがコードとテストの両方を書くと、テストは要件でなく、コードを確認するだけになりがちです。
- 緑のテストでは足りません。 高いカバレッジでも足りません:100%を覆っても、ほとんど不具合を捉えないスイートがあります。
- 正しい問いは「テストは緑か」ではなく、**「コードが間違っていたら、テストは赤になるか」**です。
- 7つの方法:仕様から書く、先に赤を見る、自分で壊す、ミューテーションテスト、プロパティベース、境界値の表、実際に動かす。
- 最も安いことから:手でコードを壊して、テストを見る。
むずかしい言葉をやさしく#
| 用語 | やさしい意味 |
|---|---|
| テスト | 別のコードを動かし、結果を期待と比べるコード。 |
| カバレッジ | テストが通るコードの行の割合。テストが正しく確かめているかは言わない。 |
| ミューテーションテスト | コードをわざと少し壊して、テストが赤になるか見ること。 |
| ミューテーションスコア | 壊した版のうち、テストが捉えた割合。 |
| プロパティベーステスト | いくつかの例の代わりに、常に成り立つ規則を述べ、ツールに多くの入力を試させる。 |
| 境界値 | 規則の境目にちょうどある値。たとえばちょうど50万。 |
| いつも緑のテスト | コードが間違っていても、決して失敗しないテスト。 |
1. 問題:テストを書く人が、コードも書いた人#
1人がコードを書いてからテストを書くとき、たいてい同じ理解を両方に持ち込みます。要件を誤解したなら、コードは間違い、テストも一緒に間違うので、緑になります。AIでは、さらに起きやすい:AIはコードができた後にテストを書き、そのコードが見えているので、テストはコードがしていることに合わせて作られがちです。
だからAIが書いたコードのレビューの15項目には、テストについての2つの項目があります:テストは大事なことを確かめているか、そしてAI自身が書いたテストは、コードを壊して赤になるか見ること。
これは単なる感覚ではありません。言語モデルでミューテーションに導かれてテストを生成する論文(MutGen、2025年)は、カバレッジは不具合検出の弱い指標だと論じ、**カバレッジ100%でも、ミューテーションスコアは約4%**というスイートに触れています。私は要旨を読んだので、この数字は、一般的な統計でなく、問題を示す例として扱ってください。
2. 例:50万の境界#
作り直した例。 要件:50万以上の注文は10%引き。
AIが書いたもの:
function discount(total) {
return total > 500000 ? total * 0.9 : total;
}
test("大きな注文は10%引き", () => {
expect(discount(600000)).toBe(540000);
});
test("小さな注文は割引なし", () => {
expect(discount(100000)).toBe(100000);
});
2つのテストとも緑で、カバレッジは100%。でもコードは間違っています:要件は「50万以上」なのに、コードは>を使っているので、ちょうど500,000の注文が割引されません。
手で壊す。 >を>=に変える。つまり、コードを要件に合うよう直すのです。テストを動かし直す:まだ緑。つまり2つのテストは、境界で、間違ったコードと正しいコードを見分けられません。これを「生き残った変異体」と呼びます。
仕様からテストを1つ足す:
test("ちょうど500,000は割引される", () => {
expect(discount(500000)).toBe(450000);
});
これは、今のコードでは赤で、直した後は緑です。価値のあるテストです:コードが間違っているとき失敗するのですから。
3. テストの価値を保つ7つの方法#
方法1:コードでなく仕様からテストを書く#
コードでなく要件を見て、例を書きます。記事仕様駆動開発は、変わったケースも含め、受け入れられる例を3〜6個、先に書くことを勧めます。それがまさに、必要なテストです。
AIにテストを書かせざるをえないなら、コードだけでなく、仕様と例を渡してください。
方法2:先にテストが失敗するのを見る#
直す前にテストを書き、動かし、正しい理由で失敗するのを見て、それから緑に直します。一度も赤を見ていないテストは、何かを捉えられるか分からないテストです。
Anthropicの実践ガイドは、エージェントへの依頼の例で、まさにこの考えを使っています:「再現する失敗するテストを書き、それから直して」。
方法3:自分でコードを壊す#
最も安い方法で、ツールは要りません。テストが緑になった後、コードをわざと少し壊して、動かし直します:
>を>=に、またはその逆に変える。0.9を0.8に変える。- 条件を1つ消す、または
&&を||に変える。 - 固定の値を返す。
テストが緑のままなら、テストが確かめていない場所を見つけたことになります。それから変更を元に戻します。
方法4:自動のミューテーションテスト#
方法3がすることを、体系的に行います:ツールが多くの小さな壊れた版を作り、どれが生き残るか(テストが捉えなかったか)を報告します。言語ごとにツールがあり、たとえばStryker(JavaScript)、mutmut(Python)、PIT(Java)。調べる手がかりとして挙げるもので、この記事で動かしたわけではありません。
注意:ミューテーションテストは遅く、重要でない生き残りを多く報告することがあります。プロジェクト全体でなく、重要な中核に使ってください。
方法5:プロパティベーステスト#
いくつかの例の代わりに、常に成り立つ規則を述べ、ツールが多くのランダムな入力を試します。割引の関数なら:
- 結果は、元の合計より決して大きくならない。
- 50万以上のすべての合計で、結果はちょうど90%。
- 50万未満のすべての合計で、結果は合計と等しい。
Hypothesis(Python)やfast-check(JavaScript)のようなツールがこれをします。思いつかなかった境界値を見つけることがよくあります。
方法6:境界値の表#
閾値のある規則ごとに、3つの値を書きます:すぐ下、ちょうど、すぐ上。
| 入力 | 期待 |
|---|---|
| 499,999 | 499,999(割引なし) |
| 500,000 | 450,000(割引) |
| 500,001 | 450,000.9(割引) |
さらに変わったケース:0、負の数、非常に大きい、型違い、空。
方法7:一度実際に動かす#
単体テストは、開いて試すことの代わりになりません。記事AIエージェントと働いた3時間には、論理の検査が捉えられなかった不具合があります。人の目に見えるものだからです。アプリを動かし、主な経路を触り、見たことを記録してください。
4. 弱いテストの匂い#
AIが書いたテストをレビューするとき、次の兆候を探します:
| 兆候 | なぜ弱いか |
|---|---|
検査がない(expectやassertがない) |
行を通るだけで、結果を比べない |
| 当たり前を確かめる(結果が空でない、型が合う) | ほとんど常に真 |
| 結果を自分自身と比べる | 常に真で、何も検出しない |
| すべてをモックして、モックが呼ばれたと確かめる | モックを試すだけで、振る舞いは試さない |
| 現在の出力を答えとして保存する | 既存の不具合がすべて「正しい」になる |
| エラーを捕まえて飲み込み、緑と報告する | 失敗を隠す |
| 1つのきれいな値だけ試す(600,000のような) | 境界と変わったケースを見逃す |
| コードと同じ式で期待値を計算 | 同じ間違いは、一緒に通る |
どのテストも、答えられるべきです:「どんな間違ったコードなら、これは赤になるか」。
5. AIが書いたテストをレビューする6つの問い#
- テストは、仕様から書かれたか、コードから書かれたか。
- 少なくとも一度、失敗するのを見たか。
- コードを少し壊すと、テストは赤になるか。(大事な所で、少なくとも一度試す。)
- すべての閾値に境界値の表があるか。
- 本当の検査(通るだけでない)があり、余計なモックがないか。
- アプリの主な経路を実際に動かしたか。
6. AIにテストを頼むときの頼み方#
私の提案です。 「この関数のテストを書いて」の代わりに、こう試します:
「これが仕様と、受け入れられる3つの例(上の例)。仕様からテストを書いて、今あるコードからではなく。各閾値のすぐ下、ちょうど、すぐ上の値を含めて。そのあと、コードを3通りにわざと壊して、どのテストが赤になるか教えて。動かしたコマンドと出力を見せて。」
最後の依頼は、エージェントが自分で確かめる方法でもあり、記事エージェントへの依頼を書くときの7つの間違いのとおりです。
7. バランスの取れた見方#
- この記事の数字は断片にすぎません。 MutGenの論文は全文を読んでおらず、このプロジェクトのAI製コードについての私自身の測定もありません。
- ミューテーションテストは時間がかかります。 選んで使い、100%のスコアを追わないでください。
- テストだけではありません。 コードレビュー、公開後の不具合の監視、実際に動かすことも要ります。
- 良いテストにも保守の費用があります。 細かいテストが多すぎると、コードを変えるのが遅くなることがあります。
- 50万の例は作り直したものですが、不具合の種類(境界での間違い)は、とても現実的で、よくあるものです。
8. 結論#
緑のテストが言うのは、テストとコードが互いに一致していることだけで、両方が正しいことではありません。AIが両方を書くと、その一致は、同じ誤解から生まれやすい。問う価値のあるのは、**「コードが間違っていたら、テストは赤になるか」**で、知る最も安い方法は、コードを壊して見ることです。
今日の小さな一歩:AIが書いたテストのある重要な関数を1つ選び、その中の比較の記号か数字を1つ変えて、テストを動かし直してください。緑のままなら、書くべき最初のテストを見つけたことになります。
参考資料#
よくある質問
- なぜAIが書いたテストは、いつも緑になりがちなのですか
- AIは、たいていコードを書いた後にテストを書き、要件が求めることでなく、コードがしていることに合わせてテストを作るからです。コードが境界で間違っていれば、そのコードから作ったテストも同じように間違い、緑のままです。AIは、値が存在するかだけを確かめる、当たり前のことを確かめるといった弱い検査も書きがちです。
- テストのカバレッジ100%なら、コードは安全ですか
- いいえ。カバレッジは、テストがその行を通るかだけを測り、正しい結果を確かめているかは測りません。ミューテーションに導かれたテスト生成の論文(MutGen、2025年)は、カバレッジは不具合検出の弱い指標だと論じ、カバレッジ100%でもミューテーションスコアが約4%のスイートに触れています。私は要旨を読んだだけで、全文は読んでいません。
- ミューテーションテストとは何ですか
- コードをわざと少し壊し(たとえば「より大きい」を「以上」にする)、テストを動かし直すことです。テストが緑のままなら、テストはその変化を検出できなかったということです。ミューテーションスコアは、壊した版のうちテストが捉えた割合です。
- 特別なツールは要りますか
- 必須ではありません。最も安い方法は、手でコードを壊すことです:記号や数字を1つ変えて、テストが赤になるか見る。自動化するなら、言語ごとのミューテーションツール(たとえばJavaScriptのStryker、Pythonのmutmut、JavaのPIT)がありますが、この記事では動かしていません。