Codexにpytestを書いてもらい、全件が緑になった。それでも「本当にバグを見つけられるテストなのか」と不安になることがあります。
実装と同じ勘違いをテストの期待値へ写してしまうと、コードもテストも間違ったまま全件成功します。そこで、コードより先に人が仕様表を固定し、正常実装で通ることと、意図的に入れた小さな不具合で狙ったケースが落ちることを対で確認します。
この記事では、記事専用に作ったCSV 1行の検証関数を題材にします。実在の業務コードや顧客データ、外部APIは使っていません。
先に「正解」を表にする
例の関数は、record_id、email、ageの3列を受け取ります。年齢は18歳以上65歳以下を正常としました。ここで大切なのは、期待結果を現行実装やCodexの出力から逆算しないことです。
今回は、テスト生成前に次の10ケースを固定しました。
| ケースID | 入力の要点 | 期待結果 |
|---|---|---|
valid-middle |
年齢30 | 正常 |
lower-before-17 |
下限直前の17 | ValueError |
lower-boundary-18 |
下限の18 | 正常 |
lower-after-19 |
下限直後の19 | 正常 |
upper-before-64 |
上限直前の64 | 正常 |
upper-boundary-65 |
上限の65 | 正常 |
upper-after-66 |
上限直後の66 | ValueError |
empty-age |
年齢が空文字 | ValueError |
missing-column |
2列しかない | ValueError |
age-is-int |
文字列ではなく整数の30 | TypeError |
「境界値も入れて」と頼むだけでは、どの値が境界なのかをCodexが推測する余地が残ります。18と65を含むのか、空文字と列不足を同じエラーにするのかまで、人が先に決めます。決められない項目があれば、テスト生成を始めず仕様確認へ戻します。

Codexへ対象と完了条件をまとめて渡す
Codexには、対象ファイルだけでなく、変更してよい範囲と禁止事項も伝えました。保存した英語の依頼文を日本語で要約すると、次の内容です。
|
1 2 3 4 5 6 |
AGENTS.md、specification.md、src/csv_validator.pyを読み、 固定済みの表にある全ケースのtests/test_csv_validator.pyを作る。 共通の検査にはpytest.mark.parametrizeを使い、各パラメーターへ 表と同じケースIDを付ける。異常系は例外型とメッセージを確認する。 期待値を実装から導かない。本体、仕様、依存関係は変更しない。 python -m pytest -qを実行し、結果と変更ファイルを報告する。 |
繰り返し使うテストコマンドや変更禁止範囲はAGENTS.mdへ、今回だけのCSV仕様は依頼文と仕様ファイルへ分けています。AGENTS.mdの置き方から確認したい場合は、AGENTS.mdでCodexに開発ルールを伝える最小テンプレートも参照できます。
採用前には、Codexが作ったテスト以外のファイルに変更がないかも確認します。今回の生成差分はtests/test_csv_validator.pyの追加だけでした。
parametrizeで仕様表とケースを対応させる
正常系は、入力と期待する辞書を並べました。idを仕様表と同じにすると、失敗した条件を出力から追いやすくなります。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@pytest.mark.parametrize( ("row", "expected"), [ pytest.param( ["u-001", "user@example.com", "18"], {"record_id": "u-001", "email": "user@example.com", "age": 18}, id="lower-boundary-18", ), pytest.param( ["u-001", "user@example.com", "65"], {"record_id": "u-001", "email": "user@example.com", "age": 65}, id="upper-boundary-65", ), ], ) def test_validate_row_accepts_specified_rows(row, expected): assert validate_row(row) == expected |
異常系では、単に「何か例外が出た」では合格にしません。型違いならTypeError、列不足や範囲外ならValueErrorというように型を限定し、今回はメッセージ全体も照合しました。
|
1 2 3 |
with pytest.raises(exception_type, match=f"^{re.escape(message)}$") as excinfo: validate_row(row) assert excinfo.type is exception_type |
pytest公式資料では、pytest.mark.parametrizeで同じテスト関数へ複数の引数セットを渡せます。また、pytest.raises()は指定した例外型のサブクラスにも一致します。厳密な型が必要なコードでは、excinfo.type is ExpectedErrorのような追加確認も検討します。
1行だけ壊し、狙ったテストが落ちるかを見る
正常実装の範囲判定は次の形です。
|
1 2 |
if age < 18 or age > 65: raise ValueError("age must be between 18 and 65") |
Python 3.11.13、pytest 9.1.1、Linux x86_64の単一環境で、最初に10件すべてが成功しました。
|
1 2 |
.......... 10 passed in 0.07s |
次に、実行前の計画どおり、上限の比較演算子だけを>から>=へ変えました。
|
1 2 |
-if age < 18 or age > 65: +if age < 18 or age >= 65: |
この故障実装では、65歳を正常とするupper-boundary-65だけが失敗しました。構文エラーや起動失敗ではなく、事前に狙った仕様ケースが落ちています。
|
1 2 |
FAILED tests/test_csv_validator.py::test_validate_row_accepts_specified_rows[upper-boundary-65] 1 failed, 9 passed in 0.16s |
比較演算子を元へ戻して同じコマンドを実行すると、再び10件すべてが成功しました。
|
1 2 |
.......... 10 passed in 0.07s |
この「正常→1か所だけ故障→復元」を行うと、少なくとも計画した境界不具合を対象テストが検出することを確認できます。ただし、これだけですべての不具合を検出できる、品質を保証できるという意味ではありません。テスト件数やカバレッジ値だけでも保証にはなりません。
外部APIのモックは導入前にここを確認する
今回の実測はCSV行の検証だけで、外部APIのモック、予期しない通信を止めるfixture、別のPython/pytest版、別OS、CIは試していません。そのため、これらを成功例としては扱わず、導入前の確認項目に留めます。
- 正常応答だけでなく、タイムアウトと形式不正もfixtureにする
- 実ネットワークや実トークンを使わず、記事・テスト専用の値にする
- ライブラリ全体ではなく、テスト対象コードが参照する名前を差し替える
- モックの戻り値も、人が決めた仕様表と対応させる
- 通信を試みたらテストを失敗させる仕組みを別途確認する
pytest公式のmonkeypatch資料には、属性や環境変数などをテスト中だけ差し替え、終了後に戻す方法が示されています。実際のコードへ導入するときは、patch先を誤ると本物の通信が残るため、通信禁止の確認まで含めてから採用します。
差分レビューとCIへ進める条件
生成後は、合否だけでなく差分を人が確認します。
- 本体コードや仕様ファイルを無断で変えていないか
- 期待値を実装の戻り値から作る自己参照がないか
- 常に真になる
assertがないか - 仕様にない期待値や不要な依存関係を足していないか
- 各ケースIDが仕様表の1行と対応しているか
- fixtureや出力に秘密情報が入っていないか
ローカルで正常、故障、復元の3段階が揃い、差分レビューを通ったテストだけをCI候補にします。CIへ組み込む場合も、pytestの合否とCodex CLI処理自体の終了状態は別の確認対象です。非対話実行まで広げる場合は、Codex CLIのJSONL・終了状態・成果物差分を確認する手順が次の参考になります。

まとめ
Codexが生成したpytestは、全件成功だけで採用を決めません。まず人が仕様表を作り、その表から期待値を引かせます。次に正常実装で通し、1か所だけ変えた故障実装で事前に狙ったケースが落ちることを確認し、元へ戻して再び通します。
今回確認できたのは、単一のPython/pytest環境における10ケースと、上限比較の1行故障です。外部APIのモック、別OS・別版、CI、WordPress保存後のコード表示は未確認です。採用環境では、同じ順序で入力、差分、完全な出力、終了状態を残してください。
資料・検証確認日:2026年10月13日。参照:pytest「How to parametrize fixtures and test functions」、pytest「How to write and report assertions in tests」、pytest「How to monkeypatch/mock modules and environments」、OpenAI「Custom instructions with AGENTS.md」。公開直前に公式資料と採用バージョンを再確認してください。
著者・監修者:タケナカ

