この記事は、連載「読まない技術」の第 3 回です。各回はファイルやスクリプトを一つ置けば完結します。連載の全体像と各回の一覧は序論にあります。
今回は、AI が書いた単体テストの話です。通っているテストが本当に何かを守っているのかを、テストを読まずに確かめます。
まず一つだけ、試してみてください。AI に書かせたテストが通っている実装を開き、実装の 1 行をわざと壊します。境界値を 1 つずらす、条件を反転する、定数を書き換える。それでテストを走らせてください(見終わったら、壊した 1 行は必ず元に戻します)。
落ちれば正常です。通ってしまったら、そのテストは最初から何も見ていません。あなたのプロジェクトの緑のチェックマークのいくつかは、この状態です。
通ってしまう理由は、AI に固有の構造です。AI は実装とテストを同時に書きます。同じ理解、同じ思い込み、同じ手で両方を書くので、テストが実装の写しになる——実装の出力をそのままアサーションに貼る、モックの中で実装と同じ計算をする、実装が見ていない場所はテストも見ていない。人間のチームがレビューで防いでいた自作自演が、AI では標準の生産形態になります。
私の手元の本番プロジェクト——typingtube という、YouTube の音楽動画でタイピング練習ができる Web サービスです——には、この方法で「通ってしまうテスト」を見つけて直した形跡が、テストのコメントに 26 か所残っています。「学習帳(単語帳でタイピング練習する機能)の首都データを別の都市に書き換えても合格のままだった」「2 つの検証のうち片方を壊しても、もう片方が隠して気づけなかった」——どれも読んで見つけたものではありません。写しは、読み物としては正しく見えるからです。
理解したことは一文にできます。
AI は実装とテストを同時に書くから、テストが実装の写しになる。写しかどうかは、壊してみたときにしか分からない。
CC BY 4.0
「ミューテーションテストは遅くて割に合わない」への答え
いまやったことにはミューテーションテスト(変異テスト、mutation testing)という名前があります。経験者ほど「有効だが、全変異を機械生成して回すのは遅く、大半のプロジェクトで割に合わない」という認識を持っているはずです。その認識は正しくて、ここでは前提が二つ違います。
対象が違う——人間が書いたテストの網羅度測定ではなく、AI が書いたテストの自作自演検出です。やり方が違う——専用ツールで全変異を生成するのではなく、AI 自身に数か所だけ選ばせて注入させます。どこが壊れたら困るかを一番知っているのは、実装を書いた AI 自身だからです。
仕組み: 方針ファイルを 1 枚置く
置くのはこれだけです(手元の運用からの簡約版)。
# 変異注入の方針(テストの点検を頼まれたら読む)
目的: AI が書いたテストが「実装の写し」になっていないか調べる。
変異 = 実装を 1 か所だけ意図的に壊すこと。壊しても通るテストは、その箇所を見ていない。
1. 変異は「テストが落ちるはずの行」を選ぶ。境界値・条件分岐・計算式を優先する
(当たらないはずの行を壊して通っても、それはテストの穴ではない)
2. 1 か所壊す → テストを走らせる → 結果を記録 → 必ず元に戻す(git diff が
空に戻ったことまで確認する)。これを数か所
3. 最初の 1 件は「確実に落ちる変異」にする。全部通ったら、まず測定器の側を疑う
4. 落ちた変異は何も保証しない。詳細を報告するのは「通ってしまった変異」だけ
(落ちた側は件数のみ)
5. 通ってしまったテストは、実装の写しではなく仕様から書き直す
使い方は「この方針を読んで、今日書いたテストに変異を入れてみて」と頼むだけ。どこに何か所入れるかの判断は AI がします。方針の 1 と 3 は、AI が実際に踏んだ落とし穴です。設計上当たらないのが正しい行を壊して「テストの不備だ」と AI が騒いだこともあれば、何を壊しても合格のままでテスト自体が走っていなかったこともあります。
報告の読み方が一つだけあります。「5 か所壊して 5 か所落ちました」は成果ではありません。落ちた変異は何も保証しない——写しのテストでも、写した範囲の変異なら落ちます。意味があるのは通ってしまった変異だけで、1 件出たらこの方針ファイルは元を取っています。
CC BY 4.0 はここまで
読まなくなったもの
AI が書いたテストの本文です。私は、「ちゃんとテストできているか」をテストコードを読んで判断する作業を、壊して確かめる作業に置き換えました。読む目は写しに騙されますが、変異は騙されません。
CC BY 4.0
注意: 変異は抜き取りで、壊す時間がある
数か所の変異は抜き取りであって、網羅ではありません。通った変異が見つかればそのテストは確実に直せますが、全部落ちても「品質は万全」にはなりません。AI の報告は「5 か所すべてが落ちました」で終わります。落ちた変異は、実装を写しただけのテストでも落ちるからです。 この 1 行を品質の証拠として受け取った時点で、この方針ファイルは偽の安心に変わります。
もう一つ。変異の最中は、わざと壊した実装がその場に生きています。方針 2 の「必ず元に戻す」が全体の要で、戻し切る前にコミットや並列実行中のセッションの作業が走ると、意図した欠陥がそのまま先へ進みます。
検証手順: 測定器のほうから確かめる
前提
- この回の方針ファイルを 1 枚置き終えていること
- AI が書いたテストが 1 ファイル以上あり、そのテストが今は全部通っていること
- 作業中の変更が無い状態(
git statusが空)から始めること。⚠️ 後始末で戻すものと、あなたの編集を混ぜないためです
所要時間: 15 分
手順
- AI に、既存のテスト 1 ファイルへの変異注入を頼みます——「この方針を読んで、
<テストファイル名>が守っている実装に変異を入れてみて」。どこに何か所入れるかは AI が決めます - あなたが、報告を 3 つに分けて読みます——落ちた変異の件数、通ってしまった変異の件数、そして通ってしまったものの中身(どこを壊したか)
合格条件(すべて満たすこと)
- 落ちた変異が 1 件以上ある
- 通ってしまった変異が 0 件である
合格しなかったとき
- 通ってしまった変異が 1 件以上あった —— それが収穫です。 そのテストは、壊した箇所を見ていません。AI に、そのテストを実装からではなく仕様から書き直させます
- 落ちた変異が 0 件(全部通った) —— ⚠️ 変異の質を疑う前に、テストが実行されているかを疑ってください。 ファイル名の指定違いや、そのテストが実行の対象から外れている可能性が先です
後始末
- 変異を入れた実装を、全部元に戻します。
git diffが空になることを目で確かめます - ⚠️ ここが最重要です。戻し切る前にコミットや他のセッションの作業が走ると、わざと入れた欠陥がそのまま先へ進みます
CC BY 4.0 はここまで
次回は「スキルを使わない技術」。私は、AI にスキルを登録するのをやめました。それでも AI は、多数の“スキル”を使いこなしています。
連載「読まない技術」
- ← 前回: 第 2 回 メモリを読まない技術
- → 次回: 第 4 回 スキルを使わない技術
- 全回の一覧: 序論 AIの出力を、もうほとんど読んでいない