0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

🧪pytestが通るだけでは安心できない|CodexとCosmic Rayで2度目のMutation Testing

0
Last updated at Posted at 2026-10-07

3969.png

📌 この記事を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の検知力を確かめる 増えた機能を広く再監査する

📌 今回の「手動Mutation」について

今回の手動Mutation 60件は、私自身が60件のコードを手作業で書き換えたという意味ではありません。

Cosmic Rayなどの専用ツールで自動生成せず、個別に指定したMutationをCodexに実行してもらう方式です。

その後のCosmic RayによるMutationも、Codexを通して実行・管理しています。

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による変更を検知して初めて、そのテストによって対象の故障を検知できたと判断します。

📌 手動方式60件で確認できたこと

8月の11件に続き、今回もMutationによって正式pytestの検証不足を発見し、強化できました。

ただし、60件の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種類だけにはしませんでした。

⚠️ Mutationの実行結果とテスト品質は同じではない

KILLED数が多いことは参考になりますが、それだけでテスト全体の品質が保証されるわけではありません。

SURVIVEDやERRORについても、原因を確認する必要があります。

ツールが出した判定を、そのまま最終的な評価にしないことを意識しました。


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の無料環境で公開しているため、スリープ状態からの起動時に少し時間がかかる場合があります。

0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?