エンジニアリング

AIが書いたコードのテスト:いつも緑のテストを避ける

AIはコードと、そのテストの両方を書くので、テストは書いたばかりのコードをただ確認するだけになりがちです。この記事は、緑のテストでは足りない理由を説明し、50万の境界の例を示し、テストの価値を保つ7つの方法を挙げます:仕様から書く、先に赤を見る、自分でコードを壊す、ミューテーションテスト、プロパティベーステスト、境界値の表、実際に動かす。

  • #テスト
  • #AI
  • #ミューテーションテスト
  • #プロパティベーステスト
  • #品質

スライド

スライドをクリックし、矢印キーでページを移動します。Lキーで言語、Fキーで全画面表示を切り替えます。

全画面で開く

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つの問い#

  1. テストは、仕様から書かれたか、コードから書かれたか。
  2. 少なくとも一度、失敗するのを見たか。
  3. コードを少し壊すと、テストは赤になるか。(大事な所で、少なくとも一度試す。)
  4. すべての閾値に境界値の表があるか。
  5. 本当の検査(通るだけでない)があり、余計なモックがないか。
  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)がありますが、この記事では動かしていません。