はじめに
AIにテストコードを書かせると、数分で数十本のテストが出てきます。困るのはそのあとで、どれが本当に役に立っているのか、誰がどこで確かめるのかが決まっていないと、通るだけのテストが増えていきます。
この記事では、AIを使ってテストを書くときに、計画・作成・デバッグ・レビューの4つの段階ごとに人が確かめる点を置く「検証ループ」を、pytestの小さな例で説明します。各段階で、AIが間違えやすいところも書いています。気になった段階だけ拾ってもらっても大丈夫です。
コードの動作確認環境は Python 3.14.6、pytest 9.1.1 です。例は説明用に作った最小のものです。
この記事をざっくり図解
前提: AIに任せる作業と、人が持つ作業
分け方の目安は、難しいかどうかより、正解が一つに決まるかどうかです。
| 正解が決まる作業 | 判断が要る作業 | |
|---|---|---|
| 例 | テストデータの生成、仕様からテストの骨組みを作る、ログの分類 | 仕様が曖昧なときの解釈、どこまでテストするか、リリースしてよいか |
| 主に担当 | AI(人が確かめる) | 人(AIは材料を出す) |
| 分け方を間違えると | 人の時間がむだになる | 不具合がそのまま本番に出る |
もう一つ、AIに渡す作業は短く切ったほうが成功しやすいです。METRの研究では、AIが50%の確率で完了できる作業の長さは2019年以降およそ7か月ごとに倍になっている一方で、長い作業ほど完了率は下がるとされています。テスト一式をまとめて任せるより、段階ごとに区切って渡すほうが確かめやすくなります。
例に使う仕様と実装
ECサイトの送料計算を例にします。仕様は次の3行です。
- 税込小計が5,000円以上なら送料無料
- それ未満は送料550円
- 離島への配送は、別途1,100円を加算
実装には、わざと1か所バグを入れてあります。
FREE_SHIPPING_THRESHOLD = 5000
BASE_FEE = 550
REMOTE_ISLAND_EXTRA = 1100
def calc_shipping_fee(subtotal: int, remote_island: bool = False) -> int:
"""税込小計(円)から送料(円)を返す。"""
if subtotal < 0:
raise ValueError("subtotal must be >= 0")
fee = 0 if subtotal > FREE_SHIPPING_THRESHOLD else BASE_FEE
if remote_island:
fee += REMOTE_ISLAND_EXTRA
return fee
仕様は「5,000円以上」なのに、実装は >(より大きい)になっています。ちょうど5,000円の注文に送料がかかってしまうバグです。
1. 計画: 観点はAIに出させ、仕様の穴は人が埋める
最初に、仕様と実装を渡してテストの観点を出してもらいます。
次の仕様と実装に対して、テストの観点を列挙してください。
コードはまだ書かないでください。
仕様から判断できない点があれば、推測で埋めずに「未確定」として挙げてください。
最後の1行が大事な部分です。この仕様には、書かれていないことがあります。5,000円以上の離島配送は、0円なのか1,100円なのか。実装は1,100円を返しますが、それが仕様どおりなのかは誰も決めていません。
AIは、こうした穴を実装の動きに合わせて黙って埋めがちです。そのままテストを書かせると、「5,000円以上の離島は1,100円」というテストができあがり、確認されていない仕様がテストで固定されます。
この段階で人が見る点は、未確定の項目が挙がっているかどうかです。挙がっていなければ、自分で探します。未確定の項目は仕様の持ち主に聞き、返ってきた答えを仕様の文書かチケットに残してから次へ進みます。
2. 作成: 「仕様の言い換え」になっているテストを捨てる
観点が固まったら、テストを書いてもらいます。何も指示しないと、次のようなテストが出てくることがよくあります。
from shipping import calc_shipping_fee, FREE_SHIPPING_THRESHOLD, BASE_FEE
def test_small_order_has_fee():
assert calc_shipping_fee(3000) == BASE_FEE
def test_large_order_is_free():
assert calc_shipping_fee(10000) == 0
def test_fee_matches_rule():
for subtotal in (1000, 4000, 8000):
expected = 0 if subtotal > FREE_SHIPPING_THRESHOLD else BASE_FEE
assert calc_shipping_fee(subtotal) == expected
実行すると、すべて通ります。
$ pytest -q test_shipping_ai_draft.py
... [100%]
3 passed in 0.00s
バグがあるのに全部通るのは、このテストに2つの問題があるからです。
1本目と2本目は、3,000円や10,000円のような「真ん中の値」しか試していません。バグがあるのは境界の5,000円ちょうどです。
3本目はもっと見つけにくい問題です。期待値を、実装と同じ subtotal > FREE_SHIPPING_THRESHOLD という式で計算しています。実装の式が間違っていれば、テストの期待値も同じように間違えるので、何を入れても通ります。これは仕様を確かめているのではなく、実装を言い換えているだけです。
人が見る点は、期待値がどこから来ているかです。期待値は仕様から直接書いた値(550、0)にします。実装の定数や式を import して期待値を組み立てているテストは、書き直すか捨てます。
境界値と異常値を足して書き直したのが次のテストです。
import pytest
from shipping import calc_shipping_fee
@pytest.mark.parametrize(
("subtotal", "expected"),
[
(0, 550),
(4999, 550),
(5000, 0), # 境界: 「5,000円以上」は無料
(5001, 0),
],
)
def test_boundary_of_free_shipping(subtotal, expected):
assert calc_shipping_fee(subtotal) == expected
def test_negative_subtotal_is_rejected():
with pytest.raises(ValueError):
calc_shipping_fee(-1)
$ pytest -q test_shipping_reviewed.py
..F.. [100%]
=================================== FAILURES ===================================
____________________ test_boundary_of_free_shipping[5000-0] ____________________
subtotal = 5000, expected = 0
(中略)
> assert calc_shipping_fee(subtotal) == expected
E assert 550 == 0
E + where 550 = calc_shipping_fee(5000)
test_shipping_reviewed.py:16: AssertionError
=========================== short test summary info ============================
FAILED test_shipping_reviewed.py::test_boundary_of_free_shipping[5000-0] - as...
1 failed, 4 passed in 0.01s
5,000円で550円が返り、バグが見つかりました。作成の段階でAIに渡す指示には、最初から次の2行を入れておくと、書き直す量が減ります。
期待値は仕様の文言から直接書いた値にしてください。実装の定数や式から期待値を計算しないでください。
「以上」「未満」などの境界ごとに、境界ちょうど・1つ手前・1つ先の3つの値を入れてください。
3. デバッグ: 説明ではなく、ログで確かめる
テストが落ちたら、AIに原因を聞くと手早く整理できます。ただ、この段階のAIは、ログを見ないまま、もっともらしい原因を流暢に説明することがあります。説明がうまいことと、合っていることは別です。
そこで、原因を挙げるときは根拠を必ず添えてもらいます。
次のテストの失敗について、原因の候補を挙げてください。
各候補には、根拠となるログの行(入力値、返り値、行番号)を引用してください。
ログから根拠を示せない候補は「推測」と明記してください。
今回のログには、入力5,000に対して550が返ったことが書かれています。根拠のある候補は「無料の条件が > になっている」です。人はその行を実装で確かめてから、>= に直します。
fee = 0 if subtotal >= FREE_SHIPPING_THRESHOLD else BASE_FEE
ここで人が見る点は、直したあとに落ちていたテストが通ること、そして通っていた他のテストが落ちていないことの両方です。
4. レビュー: 新しいセッションで読ませる
4つの段階のうち、いちばん省かれやすく、いちばん残しておきたいのがこの段階です。
テストを書いたのと同じセッションでレビューを頼むと、AIは直前までの自分の考えを持ったまま読むので、自分の出したテストを肯定しがちです。レビューは新しいセッションを開き、仕様とテストファイルだけを渡します。それまでの会話は渡しません。下書きを読んでいないレビュアーと同じ立場を作るためです。
あなたはこのテストを初めて読むレビュアーです。
次の仕様とテストコードだけを根拠にしてください。
1. 仕様の各行に対応するテストがあるか。ないものを挙げる
2. 期待値を実装と同じ式で計算しているテストがあれば指摘する
3. 境界値(以上/より大きい、0、負の数)の抜けを挙げる
指摘には、仕様の該当行とテスト名を必ず添えてください。
問題がなければ「なし」と書いてください。
今回の例なら、1の答えとして「離島配送の加算(仕様3行目)に対応するテストがない」が挙がるはずです。挙がらなければ、レビューの指示か渡した材料に問題があります。
人が見る点は、レビューの結論が作成段階とまったく同じになっていないかです。指摘がゼロの回が何度も続くとき、テストがよくできているとは限りません。前の段階と同じ思い込みを共有しているか、レビューの観点が作成時と変わっていない可能性があります。
この段階を誰が担当するかも大事です。Leeらの調査(CHI 2025、知識労働者319名、936件の利用事例)では、AIへの信頼が高い人ほど確かめる手間をかけなくなり、自分の専門性に自信がある人ほど確かめる傾向がありました。AIの出力に反論できるだけの業務知識を持つ人を、この段階に置いてください。
何を測るか
生成したテストの本数は、すぐに増えますが、品質については何も教えてくれません。代わりに次の2つを記録しておくと、ループが機能しているかが見えてきます。
| 指標 | 計算 | 見方 |
|---|---|---|
| 破棄率 | 人が捨てた・書き直したテスト ÷ AIが生成したテスト | 高止まりなら、作成段階の指示を見直す |
| レビュー由来の指摘の割合 | 第4段階で見つかった指摘 ÷ 1サイクルの全指摘 | 何サイクルもゼロに近いなら、第4段階が形だけになっている |
このほかに、本番で見つかった不具合のうち、テストで拾えたはずのものを数えておくと、ループ全体の効き目を後から振り返れます。
このループが向かない場面と、この記事の限界
いくつかの場面では、4つの段階を回す手間のほうが大きくなります。
- テスト範囲全体が数人日で終わる小さな改修。4段階を回す手間が、得られるものを上回ります
- 仕様が毎日変わっている段階。計画の段階がずれた観点を出し続けます。要件が落ち着いてから使うほうが向いています
- テストの書き方の規約が文書になっていないチーム。AIは既存の規約に合わせて書くので、規約がないと毎回違う形のテストが出てきます
- 医療や金融の基幹部分など、判断の責任を特定の人までたどれる必要がある領域。計画とデバッグの補助にとどめ、テストを書く・レビューする段階には使わないほうが安全です
この記事の例は、関数1つ、仕様3行の最小のものです。画面操作を含むE2Eテストや、外部APIをモックする必要があるテストでは、各段階で確かめる点がもっと増えます。また、案件のコードや仕様をAIに渡してよいかは、使うサービスの規約と社内の情報の扱いの決まりを先に確認してください。
最後に
AIは、正解が決まっている作業を速くしてくれます。どこまでテストするか、曖昧な仕様をどう解釈するかを決めるのは人です。4つの段階は、その境目を毎回確かめるための区切りだと考えています。まずは小さなモジュール1つで、第2段階の「期待値はどこから来ているか」の確認から試してみてください。
参考文献
- Thomas Kwa ほか「Measuring AI Ability to Complete Long Software Tasks」arXiv:2503.14499(NeurIPS 2025) https://arxiv.org/abs/2503.14499
- Hao-Ping Lee ほか「The Impact of Generative AI on Critical Thinking: Self-Reported Reductions in Cognitive Effort and Confidence Effects From a Survey of Knowledge Workers」CHI 2025 https://dl.acm.org/doi/full/10.1145/3706598.3713778
- pytest documentation「How to parametrize fixtures and test functions」 https://docs.pytest.org/en/stable/how-to/parametrize.html