📌 この記事を5行でまとめると
- 2026年8月に手動Mutation 11件を実施していましたが、Bakery Hubの機能が増えたため、2回目のMutation Testingに取り組みました。
- 今回は Codexへ監査作業を指示し、774 collected / 756 passedのpytestを対象に再監査を開始しました。
- まず手動方式のMutationを60件実施し、最終的に KILLED 59 / SURVIVED 0 / EQUIVALENT 1 となりました。
- その後、Cosmic Ray 8.7.0 を導入し、自分たちで選んだ故障以外にも監査範囲を広げました。
- Codexによる大量の検証を通して、テストの検知力だけでなく、判定基準・runnerの範囲・監査証拠の管理も重要だと分かりました。
はじめに
本業でトラックドライバーとして働きながらAIを活用し、Python / Flaskを中心にWebアプリを個人開発している tosane932 です。
2026年5月12日からPythonを学び始め、現在は個人開発した作品を Tosane Works としてまとめています。
その中で一番長く開発を続けているのが、ベーカリー店舗向けWebアプリ Bakery Hub です。
前回の記事では、Bakery Hubの商品管理を見直しました。
- 18商品テンプレート
- 商品画像
- 今月 / 過去の商品履歴Catalog
- 月次Productと商品Catalogの分離
- Adminからの商品Catalog申請
- GitHub PRを利用したCatalog更新
などを追加しています。
前回の記事はこちらです。
Bakery HubはLive Demoも公開しています。
Renderの無料環境で公開しているため、しばらくアクセスがなかった後は起動まで少し時間がかかる場合があります。
すぐに画面が表示されない場合は、少し待つと起動します。
ソースコードはこちらです。
Bakery Hub:
Mutation Testingは今回が2回目
実は、Mutation Testingに取り組むのは今回が初めてではありません。
2026年8月15日に、すでに記事を書いています。
📝pytestを「事故防止台帳」として育てる 第5段階:GREENを疑い、Mutation Testingで「本当に守れているか」を検証した
当時は87件のpytestが通っていました。
そこで、
pytestがGREENでも、実際にコードを壊したとき、その故障を検知できるのか?
という疑問から、11件の手動Mutation Testingを実施しました。
結果は次のとおりです。
| 項目 | 2026年8月の検証 |
|---|---|
| 開始時のpytest | 87 passed |
| 手動Mutation | 11件 |
| 初回KILLED | 6件 |
| 初回SURVIVED | 5件 |
| 強化後のSURVIVED | 0件 |
最初は5件のSURVIVEDが見つかりました。
そこでfixtureやassertを強化し、同じMutationを再適用して、最終的には11件すべてをKILLEDにできました。
このときに、
pytestがGREENであることと、故障を検知できることは別
という問題を、実際のコードで確認しています。
今回は、その経験をもとにした再監査です。
1回目と2回目は何が違うのか
2026年8月と今回の違いを整理すると、次のようになります。
| 比較項目 | 1回目:8月 | 2回目:10月 |
|---|---|---|
| Mutation | 手動11件 | 手動方式60件から開始 |
| 作業方法 | 自分でコードを変更・検証 | Codexへ監査を指示 |
| Mutationツール | 専用ツールなし | Cosmic Rayも導入 |
| 主な対象 | 代表的な故障 | 商品・売上・認証・Guestなど広範囲 |
| 判定 | KILLED / SURVIVED中心 | ERROR・runner範囲なども精査 |
| 人間の役割 | 故障の適用・検証 | 監査方針・判定基準・終了条件の管理 |
| 主な目的 | pytestの検知力を確かめる | 増えた機能を広く再監査する |
1回目は、自分で代表的な故障を選んで検証しました。
2回目の今回は、Codexを活用することで、以前より広い範囲を確認することにしました。
なぜもう一度Mutation Testingをするのか
8月のMutation Testingから約2か月。
Bakery Hubには、その後もさまざまな機能を追加してきました。
商品登録、日次売上、Dashboard、Guest Demo、材料発注、店舗メモ、Task管理などです。
機能が増えるたびにpytestも追加してきました。
通常の開発では、
機能追加
↓
pytest追加
↓
pytest GREEN
↓
GitHub ActionsもGREEN
という流れを繰り返しています。
もちろん、これは必要な確認です。
ただ、機能が増えれば、その分だけ条件分岐や例外処理も増えます。
以前の11件だけで、新しく追加した機能まで十分に検証できているとはいえません。
今回確認したかったのは、
増え続けたpytestが、どこまで故障を検知できる状態になっているのか?
ということでした。
再監査開始時点のpytest
2026年10月3日、今回の再監査を開始した時点のBaselineは次の状態でした。
| Baseline | 件数 |
|---|---|
| collected | 774 |
| passed | 756 |
| failed | 0 |
| skipped | 18 |
| warnings | 9 |
GitHub Actionsも通っています。
pytest
↓
756 passed
↓
CI GREEN
8月の87 passedから、756 passedまで増えていました。
しかし、テストが増えたからといって、その検知力まで自動的に高くなるわけではありません。
今回も、単なるテスト件数ではなく、
実際にコードを変更したとき、どの仕様を守れているのか
を確認します。
Mutation Testingの判定をおさらい
Mutation Testingでは、アプリのコードを意図的に少しだけ変更し、pytestがその変更を検知できるか確認します。
例えば、
if quantity > 0:
を、
if quantity >= 0:
に変更します。
この場合、数量0を許可するかどうかが変わります。
その違いをpytestが検知できるかを調べます。
| 判定 | 今回の扱い |
|---|---|
KILLED |
Mutationによる故障をpytestが検知した |
SURVIVED |
コードを変更しても検知されなかった |
EQUIVALENT |
コードは変化したが実質的な動作は変わらない |
ERROR |
Mutationや実行環境などでエラーが発生した |
TIMEOUT |
制限時間内に実行が終わらなかった |
KILLEDとSURVIVEDの詳しい説明や、1回目に試した11件の具体例は、8月の記事で紹介しています。
今回は基本的な仕組みよりも、Codexを使ってどのように再監査を進めたのかを中心にまとめます。
Codexへ監査を任せる前にルールを決める
今回のように大量の検証作業をAIへ任せる場合、
Mutation Testingをしてください。
だけでは、監査範囲も終了条件も曖昧になります。
そのため、あらかじめルールを決めました。
| 項目 | 監査ルール |
|---|---|
| 作業場所 | 監査用branchで実施 |
| Mutation | 正常コードへ一時的に適用 |
| DB | 隔離されたテスト用DB |
| 本番環境 | 接続・変更しない |
| KILLED判定 | 狙った故障をpytestが検知したか確認 |
| SURVIVED | 原因を分類してから対応 |
| pytest強化 | 正式仕様に沿ったテストのみ追加 |
| restore | Mutationを適用したコードは必ず復元 |
| 証拠 | 実行結果・判定・復元状態を記録 |
| 終了 | PhaseごとのGateを確認 |
特に重視したのは、
pytestがREDになった理由まで確認すること
です。
例えば、Mutationを適用したあとに、
HTTP 500
や、
予想外の例外
が発生しただけでは、狙った仕様の検証に成功したとは限りません。
変更した条件に対応するassertが失敗したかを確認します。
また、Mutationを除去して正常コードへ戻したあと、再びGREENになることも確認します。
こうした条件を決めたうえで、Codexに監査を進めてもらいました。
Phase 0〜6|Codexで60件のMutationを検証
まずはCosmic Rayを使わず、Codexに個別のMutationを実行してもらいました。
Phase 0でBaselineと監査条件を確認し、Phase 1〜6で各10件のMutationを検証しました。
| Phase | 主な監査対象 | Mutation |
|---|---|---|
| 1 | 商品登録 | 10件 |
| 2 | 日次売上 | 10件 |
| 3 | Dataset分離 | 10件 |
| 4 | Guest関連 | 10件 |
| 5 | 店舗ツール | 10件 |
| 6 | 再送・操作順序 | 10件 |
| 合計 | 60件 |
対象にしたのは、
- 境界値の変更
- 条件式の変更
- 比較演算子の変更
- rollbackの無効化
- Dataset条件の削除
- 操作順序の変更
など、実際の不具合につながりそうな変更です。
最初から全部KILLEDだったわけではない
初回の結果は次のとおりです。
| 判定 | 初回結果 |
|---|---|
| KILLED | 55 |
| SURVIVED | 4 |
| EQUIVALENT | 1 |
| 合計 | 60 |
4件のSURVIVEDが見つかりました。
8月の検証でも5件のSURVIVEDを経験していますが、今回は別の機能で新しい検証不足が見つかりました。
実際に見つかった4つのテスト不足
SURVIVEDした4件は、次の領域です。
| 対象 | 検証不足だった内容 |
|---|---|
| Dataset分離 | 登録済み月でも別Datasetの商品が混ざらない |
| 店舗メモ複製 | 複製時の作成日時が正しく扱われる |
| Task並び順 | Taskを連続作成しても順序が崩れない |
| 売上保存 | 2行目で保存失敗した場合に全体がrollbackされる |
これらは正常なコードではpytestが通っていた箇所です。
しかし、条件を意図的に変更すると、既存pytestでは検知できない部分がありました。
そこでCodexに正式なpytestの追加・強化を指示しました。
代表例|売上保存とrollback
例えば、1回のPOSTで2商品の売上を保存する場合です。
1行目
↓
保存成功
2行目
↓
保存失敗
ここで、
1行目だけDBに残る
という状態になると、売上データが不完全になります。
正常な処理では、
すべて保存成功
↓
commit
または、
途中で保存失敗
↓
rollback
↓
今回の変更は残さない
となる必要があります。
そのため、2行目の保存失敗時に、1行目の変更もDBへ残らないことを確認するpytestを強化しました。
単に、
assert response.status_code == 200
を確認するだけでは、こうしたDB内部の状態までは分かりません。
保存されたデータそのものをassertで確認する必要があります。
手動方式60件の最終結果
4件のSURVIVEDについて正式pytestを強化し、同じMutationで再検証しました。
| 判定 | 初回 | 強化後 |
|---|---|---|
| KILLED | 55 | 59 |
| SURVIVED | 4 | 0 |
| EQUIVALENT | 1 | 1 |
| 合計 | 60 | 60 |
最終的に59件がKILLED、1件がEQUIVALENTとなりました。
GREEN → RED → GREENの確認は継続
今回も、8月のMutation Testingで経験した確認方法を継続しています。
正常コード
↓
pytest GREEN
Mutationを適用
↓
狙ったassertでRED
正常コードへrestore
↓
pytest GREEN
特に、
pytestがREDになったからKILLED
と単純に判断しないことを意識しました。
狙ったassertがMutationによる変更を検知して初めて、そのテストによって対象の故障を検知できたと判断します。
手動方式のMutationだけでは限界がある
Codexを使えば、個別のMutationを大量に実行できます。
しかし、手動方式では、基本的に人間やCodexが候補として選んだ故障を検証することになります。
例えば、
比較演算子
条件式
真偽値
数値
return
論理演算
など、コード全体にはMutation候補が数多くあります。
個別のMutationを指定する方法では、どうしても候補の選び方に偏りが生まれます。
そこで次の段階として、
Mutation候補の生成そのものを自動化する
ことにしました。
Cosmic Ray 8.7.0を導入
Python向けのMutation Testingツールを調べ、今回は Cosmic Ray を採用しました。
公式GitHub:
ドキュメント:
使用した環境はこちらです。
| 項目 | 構成 |
|---|---|
| Python | 3.12.14 |
| Cosmic Ray | 8.7.0 |
| 実行管理 | Codex |
| DB | 隔離されたテスト用SQLite |
Cosmic Rayは、PythonのコードからMutation候補を生成し、テストを実行して結果を記録できるツールです。
今回の役割分担は次のようになります。
| 担当 | 主な役割 |
|---|---|
| 人間 | 監査方針・判定基準・終了条件を決める |
| Codex | 実行環境の準備・ツール実行・結果整理 |
| Cosmic Ray | Mutation候補の生成・実行結果の記録 |
| pytest | 故障を検知できるか確認する |
手動方式とCosmic Rayの違いも整理しました。
| 比較項目 | 手動方式 | Cosmic Ray |
|---|---|---|
| Mutation候補 | 個別に選ぶ | ツールが生成する |
| コード変更 | Codexが個別に適用 | ツールで自動化 |
| 候補の範囲 | 選択した故障が中心 | 多数の候補へ拡張 |
| 結果管理 | Codexが記録・整理 | Session DBなどへ記録 |
| 注意点 | 候補選びに偏りがある | 意味の薄いMutationも生成される |
Cosmic Rayを使えば、これまで思いつかなかった故障候補も生成できます。
ただし、自動生成されたMutationがすべて意味のある故障とは限りません。
そのため、いきなりアプリ全体を対象にはせず、小さく試すことにしました。
まずは限定した範囲で試す
最初からBakery Hub全体へCosmic Rayを実行するのではなく、対象を限定しました。
対象関数を限定
↓
Mutation候補を生成
↓
少数の候補を実行
↓
結果を確認
↓
判定方法を調整
↓
次の範囲へ進む
Mutation Testingでは、候補ごとにpytestを繰り返し実行します。
そのため、通常のpytestよりも実行量が大きくなります。
また、Mutationによってはテストが終了しなかったり、予期しないエラーが発生する場合もあります。
そこで、実行件数や対象範囲を区切りながら進めました。
Phase 10A〜10B|最初のCosmic Rayパイロット
Phase 10A〜10Bでは、限定した関数に対してCosmic Rayを実行しました。
重複を除いたMutation候補は73件です。
| 判定 | 件数 |
|---|---|
| KILLED | 54 |
| SURVIVED | 18 |
| ERROR | 1 |
| TIMEOUT | 0 |
| 合計 | 73 |
手動方式の60件では、最終的にSURVIVED 0まで確認できました。
しかしCosmic Rayでは、73件のうち18件がSURVIVEDしました。
さらにERRORも1件発生しています。
ここで確認できたのは、
自分たちで選んだ故障を検知できても、自動生成された別の故障まで検知できるとは限らない
ということです。
ただし、18件すべてが正式pytestの不足とは限りません。
そのため、次はMutationの結果を分類することにしました。
SURVIVEDを一律に「テスト不足」としない
Cosmic RayがSURVIVEDと判定した場合でも、原因は複数考えられます。
| 考えられる原因 | 確認すること |
|---|---|
| 正式pytest不足 | 必要なテストが存在するか |
| assert不足 | 対象の故障を検知するassertがあるか |
| runner範囲不足 | 別の既存pytestなら検知できるか |
| EQUIVALENT候補 | 変更前後で実際の動作が変わるか |
| 未使用コード | 対象処理が実際に呼び出されるか |
| 環境依存 | 実行条件によって結果が変わるか |
例えば、
Cosmic Ray
↓
SURVIVED
と記録されたからといって、
pytestを1件追加
と即決するわけではありません。
まずは、そのMutationが何を変更したのか、既存pytestのどこまでが対象になっているかを確認する必要があります。
この分類作業は、手動Mutationのとき以上に重要になりました。
runnerの範囲でも判定が変わる
Cosmic Rayでは、実行時間を抑えるために、対象のpytestを絞ったrunnerも使用しました。
しかし、関連pytestだけを実行すると、重要な既存テストが対象外になる可能性があります。
例えば、
Cosmic Ray用runner
↓
関連pytestではGREEN
↓
SURVIVED
となっていても、
別の既存pytestを含める
↓
RED
になる場合があります。
この場合、正式pytestが不足しているとは限りません。
実際には、
Mutation用runnerに必要なpytestが含まれていなかった
という可能性があります。
同じSURVIVEDでも対応は異なる
| 原因 | 対応 |
|---|---|
| 正式pytest不足 | pytestを追加 |
| assert不足 | 既存pytestを強化 |
| runner範囲不足 | 実行対象のpytestを見直す |
| EQUIVALENT | 動作の同等性を確認して記録 |
| 環境依存 | 実行条件を見直して再検証 |
自動化した結果を、そのままテスト品質の評価に使うのは危険だと分かりました。
ERRORも「アプリのバグ」とは限らない
Cosmic Rayでは、ERRORになるMutationもあります。
ただし、
ERROR
=
Bakery Hubにバグがある
とは限りません。
Mutationによってコードが実行不能になったり、Mutationそのものが別のエラーを発生させる場合もあります。
そのため、
KILLED
SURVIVED
ERROR
TIMEOUT
は、それぞれ別の結果として扱いました。
単純に、
検知できた
検知できなかった
の2種類だけにはしませんでした。
2回目の監査で、確認方法も広がった
8月のMutation Testingでは、代表的な故障を検知できるかが中心でした。
今回はCodexとCosmic Rayによって対象が広がり、テストだけでなく、監査方法そのものも確認する必要が出てきました。
| 確認すること | 1回目 | 2回目 |
|---|---|---|
| 故障を検知できるか | ○ | ○ |
| 狙ったassertでREDになるか | ○ | ○ |
| 正常コードへ復元できたか | ○ | ○ |
| 多数のMutation候補を分類する | 限定的 | ◎ |
| runnerの対象範囲を確認する | ― | ◎ |
| ERRORや環境依存を分類する | 限定的 | ◎ |
| 監査証拠を継続的に管理する | 限定的 | ◎ |
今回新しく難しくなったのは、Mutationを実行することよりも、その結果を正しく分類し続けることでした。
例えば、Codexが大量のMutationを実行できても、
どの故障を検知できたのか?
どのテストが検知したのか?
その結果は再確認できるのか?
という部分が曖昧では、監査の信頼性が下がってしまいます。
そのため、作業の実行と結果の確認を分けて考えるようにしました。
AIに任せるほど、監査ルールを決める
今回のMutation Testingでは、Codexにかなりの作業を任せています。
Mutationの適用、pytest実行、候補分類、正式pytestの追加・強化、結果の整理などです。
ただし、AIに任せる範囲が増えるほど、先に決めておくべきことも増えます。
| 管理する項目 | 決めておくこと |
|---|---|
| 監査対象 | どの関数・機能を確認するか |
| 実行範囲 | 1回に何件のMutationを扱うか |
| KILLED判定 | 何を検知の証拠とするか |
| SURVIVED分類 | どの条件でpytest不足と判断するか |
| 証拠 | 何を保存するか |
| restore | どの状態へ戻すか |
| 終了Gate | 何を確認したらPhaseを終了するか |
| full pytest | どの段階で全体を確認するか |
特に、Mutationを適用する作業では、正常なproduction codeを一時的に変更します。
そのため、壊したコードが残ったまま次の作業へ進まないようにする必要があります。
Mutationを適用
↓
pytest実行
↓
結果を確認
↓
正常コードへrestore
↓
GREEN確認
↓
次のMutation
という流れを守るようにしました。
Codexが作業を進めるとしても、どの条件で作業を終了させるかは、人間側で明確にする必要があると感じました。
そして監査は予想以上に大きくなった
今回の2回目のMutation Testingは、最初からPhase 25まで実施する予定だったわけではありません。
当初は、以前の11件より広い範囲を確認しようと考えていました。
そこから、
Codexで手動方式のMutation 60件
↓
Cosmic Ray導入
↓
Mutation候補を拡張
↓
SURVIVEDの原因を分類
↓
正式pytestを追加・強化
↓
同じMutationでRED確認
↓
正常コードへrestore
↓
次の機能を監査
を繰り返していくうちに、対象が広がっていきました。
Phase 0
Phase 1
Phase 2
...
Phase 10
...
Phase 20
...
Phase 25
最終的には、Phase 25まで進むことになります。
ただし、Phaseが増えたこと自体が成果ではありません。
重要なのは、それぞれのPhaseで、
どの故障を検知でき、どの結果に追加確認が必要だったか
を整理できたことです。
おわりに
2026年8月、私は11件の手動Mutation Testingを実施しました。
当時は、自分でコードを変更しながらpytestの検知力を確認しました。
そして2026年10月。
2回目の今回は、Codexを活用して再監査を始めました。
1回目:2026年8月
手動Mutation 11件
↓
5件のSURVIVEDを発見
↓
pytest強化
↓
11件すべてKILLED
2回目:2026年10月
Codexで手動方式のMutation 60件
↓
4件のSURVIVEDを発見
↓
pytest強化
↓
KILLED 59 / EQUIVALENT 1
↓
Cosmic Ray導入
↓
さらに広い範囲を監査
今回は単にMutationの数を増やしただけではありません。
Cosmic Rayを導入したことで、自分たちが選んでいなかった故障候補も確認できるようになりました。
一方で、SURVIVEDやERRORの原因を分類したり、runnerの範囲を調べたりと、監査そのものの難しさも増えました。
そして何より、今回はCodexを活用しています。
以前は自分でコードを変更して確認していましたが、今回は実行作業をAIへ任せ、その結果を監査ルールと照合しながら進めました。
AIに作業を任せることと、AIの報告を無条件に信用することは別です。
今回の再監査では、その違いも意識するようになりました。
本業のトラックドライバーでも、作業を誰かに任せたからといって、確認が不要になるわけではありません。
何を依頼したのか、どういう状態なら完了なのか、結果は合っているのか。
そうした確認は、Web開発でも大切だと感じています。
次回はPhase 25までの最終結果
次の記事では、この2回目のMutation Testingを最後まで進めた結果をまとめます。
- Mutation総数は何件になったのか
- どれだけKILLEDできたのか
- SURVIVEDやERRORをどう扱ったのか
- pytestはどこまで強化されたのか
- 監査用branchの成果をどうやって最新版mainへ反映したのか
単に大量のMutationを実行しただけで終わらせず、強化した正式pytestを通常開発へ戻すところまで記録します。
次回は、その5日間の監査結果です。
関連リンク
今回の商品管理改善からMutation Testing再監査、mainへの反映までを3本の記事にまとめました。
| 記事 | タイトル・リンク |
|---|---|
| 第1記事 | 🥐Bakery Hubの商品管理を作り直した|18商品テンプレート・履歴Catalog・Admin登録まで |
| 第2記事 | 🧪pytestが通るだけでは安心できない|CodexとCosmic Rayで2度目のMutation Testing |
| 第3記事 | 🧪Codexで挑んだ1,286件のMutation Testing|pytest再監査からmain反映までの5日間 |
その他の関連資料
| 分類 | リンク |
|---|---|
| 📖 2026年8月の初回Mutation Testing | 📝pytestを「事故防止台帳」として育てる 第5段階:GREENを疑い、Mutation Testingで「本当に守れているか」を検証した |
| 🧬 Cosmic Ray|公式GitHub | sixty-north/cosmic-ray |
| 📚 Cosmic Ray|公式ドキュメント | Cosmic Ray Documentation |
| 🔀 GitHub PR #81 | Mutation監査で強化したpytestをmainへ反映 |
| 🌐 Live Demo | Bakery Hubをブラウザで操作 |
| 💻 ソースコード | Bakery Hub |
| 📁 ポートフォリオ | Tosane Works |
Live DemoはRenderの無料環境で公開しているため、スリープ状態からの起動時に少し時間がかかる場合があります。
