ClaudeとProperty-Based TestingでPythonのバグを見つける仕組み
ClaudeがProperty-Based TestingでNumPyやPandasのバグを自律発見した研究を解説します。エージェントの5段階プロセス、実際のバグ5件、評価結果、手元での再現を紹介します。
Property-Based Testingとは何か
Property-Based Testingは、個別の入出力例ではなく「コードが満たすべき一般的な性質」を検証するテスト手法です。たとえば「JSONのシリアライズとデシリアライズは互いに逆演算である」という性質と、受け付ける入力の種類(JSON化できるあらゆるオブジェクト)を定義しておけば、フレームワークが性質を破る反例を自動生成して探します。ファジングに近い技術ですが、探索対象が具体的なクラッシュではなく論理的な不変条件である点が違います。
Anthropicは2026年1月14日、Claudeを使ってこの手法を自動化するエージェントの研究成果を公開しました。著者はMuhammad Maaz氏(MATS)、Liam DeVoe氏(Northeastern University)、Zac Hatfield-Dodds氏とNicholas Carlini氏(Anthropic)の4名です。研究成果は2025年のNeurIPS Deep Learning for Code Workshopで発表され、論文はarXiv(2510.09907)で読めます。発見したバグの一覧サイトも公開されており、検証前のものや無効と判定したものまで含まれています。
同じバグを探す2つの書き方
例示ベースのテスト
開発者が思いついた具体例しか検証できません。エッジケースを想定していなければ、実装にもテストにも同じ抜け漏れが残ります。
Property-Based Testing
性質と入力ドメインだけを書き、反例の探索はフレームワークに任せます。個々のエッジケースを開発者が思いつく必要がなくなります。
同じ発想は、Claude Codeにscikit-learnのモデル実装を任せて評価コードを検証するときにも使えます(Claude Codeでscikit-learnモデルを実装する)。
性質を書くとバグはどう見つかるか
ソートなら「結果が昇順に並んでいる」、シリアライズなら「戻すと元に戻る」が性質です。実際のバグに近い形で試すため、見つかった5件のうちcloudformation-cli-java-pluginのitem_hash()と同じ型の欠陥を、手元で簡略化して再現してみます。リストを.sort()してからハッシュを取るつもりで、.sort()の戻り値(None)をハッシュしてしまう関数です。
from hypothesis import given, strategies as st
def item_hash(items):
return hash(items.sort()) # list.sort() は None を返す
@given(st.lists(st.integers(), min_size=1), st.lists(st.integers(), min_size=1))
def test_different_lists_differ(a, b):
if sorted(a) != sorted(b):
assert item_hash(list(a)) != item_hash(list(b))
if __name__ == "__main__":
test_different_lists_differ()性質は「中身が違うリストは違うハッシュになる」の1行です。Hypothesis 6.168.3(Python 3.12.2)で実行すると、反例が自動で最小の形まで縮められて表示されます。
pip install hypothesis
python t_hash.pyFailing test case: test_different_lists_differ(
a=[0],
b=[1],
)[0]と[1]という最小の組が出るのは、どのリストでもhash(None)と同じ値になるからです。具体例を1つ書く例示ベースのテストでは、同じ入力のハッシュを比べる限りこの欠陥は露出しません。違う入力を比べるという性質を立てて初めて破られます。
エージェントは5段階でバグを探す
このエージェントはカスタムのClaude Codeコマンドとして作られています。引数には単一のPythonファイル(normalizers.pyなど)、モジュール、関数のいずれかを渡せます。例はnumpyやscipy.signal、requests.getやjson.loadsです。渡すと次の手順で動きます。
エージェントの動き
- 1
理解する
対象コードを読み、関連ドキュメントを引き、コードベース内での位置づけを調べます。
- 2
性質を提案する
型注釈・docstring・関数名・コメントを根拠に、成り立つべき性質を導きます。
- 3
テストを書く
Pythonのプロパティベーステストフレームワーク
Hypothesisでテストコードを書きます。 - 4
実行して自己反省する
失敗したなら、本当にバグを見つけたのかテスト側の誤りなのかを判断します。通ったなら、そのテストが自明なものになっていないかを点検します。
- 5
レポートを書く
本物のバグだと確信できたときだけ、整形したバグレポートを出力します。
重視されたのは誤検知(false alarm)を減らすことです。性質を明示的な使われ方とドキュメントに根拠づけることと、4段目の自己反省が、その手段になっています。ある実行例では、最初に書いたテストが通ったものの、自己反省の過程でテスト全体をtry-catchで囲んでいたと気づきました。それを外すとテストが失敗し、本物のバグが見つかっています。自己反省の質はOpus 4.1とSonnet 4.5でSonnet 4より目に見えて良くなったと報告されています。長い多段階の推論を支えるため、進行状況はTODOリストで管理します。
同じ形のコマンドは自分でも組めます。.claude/commands/に置いたMarkdownファイルは同名のスラッシュコマンドになり、$ARGUMENTSに渡した引数が展開されます。以下は上の5段階に沿った筆者の例で、研究で使われた指示文そのものではありません。
---
description: 指定した Python のファイルやモジュールの性質を推定し、Hypothesis でバグを探す
argument-hint: [file-or-module]
---
$ARGUMENTS を対象に、次の順で進めてください。
1. コードとドキュメントを読み、コードベースでの使われ方を把握する
2. 型注釈・docstring・関数名・コメントを根拠に、成り立つべき性質を挙げる
3. 各性質を Hypothesis のテストとして書く
4. 実行し、失敗したら「本当にバグか、テストの誤りか」、通ったら「自明なテストではないか」を点検する
5. 本物のバグと確信できたものだけ、再現手順つきのレポートにする
進行状況は TODO リストで管理してください。.claude/commands/pbt.mdとして保存すれば/pbt numpy.randomのように呼べます。スラッシュコマンドの作り方はカスタムスラッシュコマンドにまとめています。
100を超えるPythonパッケージで検証した結果
対象はNumPy・SciPy・Pandasを含む100を超える人気のPythonパッケージです。数値計算からパース処理、データベースまで幅広い領域が含まれ、エージェントを各パッケージに実行してバグレポートを集めました。
第1段階の評価(Opus 4.1)
レビューした50件のうち有効なバグ
56%
有効かつ報告に値するバグ
32%
有効だが些細で報告には値しないものを除く
上位スコアのレポートで有効
86%
arXiv版では上位21件が対象。報告に値するのは81%
人手レビューの結果をもとに、バグを15点満点で順位づけする採点基準を作り、Opus 4.1に全レポートを採点させたのが上位スコアの数字です。順位づけが効くことで、開発者に見せる順番を絞れます。
第2段階ではSonnet 4.5を使い、重要な10パッケージに複数回実行しました。バグレポートの正確性と深刻度をSonnet 4.5が判定する評価エージェントを別に作り、第1段階の採点基準より精緻な判定にしています。高深刻度のバグは、報酬を支払った3名の有識者が正しさを評価しました。
報告までに人手がどれだけかかるか
生成されたレポートをそのまま報告しているわけではありません。優先度が最も高いバグだけを選び、3名の有識者が1件あたり平均1時間ほどかけてレビューします。3名のうち1人でも有効性に確信が持てなければ、そのバグは捨てます。最後に著者自身が目を通し、確信が持てたものだけを手作業でメンテナーに報告しました。著者は、自分たちのコードレビューで普段やる以上に手間のかかる手順だと述べています。誤検知でメンテナーに負担をかけないための選別で、自動生成の件数がそのまま報告件数にならない理由でもあります。
実際に見つかった5件のバグ
研究チームは特に興味深い5件を選び、パッチ案とともに各リポジトリへ手動で報告しました。バグの有効性は、最終的にメンテナーが判断します。
numpy.random.waldは、本来は正の値しか返さないはずのWald分布のサンプルが、ときどき負の値を返す不具合でした。エージェントはWald分布の性質として「すべてのサンプルが正の値である」ことを検証するテストを書きました。エージェント自身が出した修正案は誤りで、研究者が原因を桁落ち(catastrophic cancellation)による数値不安定な計算までたどり、より安定した再定式化を考案しています。NumPyのメンテナーがプルリクエスト上で示した結果では、相対誤差が従来のアルゴリズムより約10桁小さくなりました。パッチは#29609としてマージ済みです。
aws-lambda-powertoolsのslice_dictionary()は、イテレータを進め忘れていたため、常に最初のチャンクだけを繰り返し返しました。辞書をスライスして再構築すると元の辞書と一致するはずという性質のテストで発見され、#7246としてマージされています。
cloudformation-cli-java-pluginのitem_hash()は、インプレースの.sort()(戻り値はNone)を使っていました。そのため、すべてのリストでhash(None)と同じ値になっていました。上で再現した型の欠陥です。異なる入力は異なるハッシュ値を持つはずという性質で見つかり、#1106として修正案が提出されています。
tokenizersのEncodingVisualizer.calculate_label_colors()は、出力するHSLのCSS文字列の閉じ括弧が欠けており、無効な値を返していました。出力がHSLカラーコードの正規表現にマッチするかを調べるテストで発見され、#1853としてマージ済みです。
python-dateutilのeaster()は、ユリウス暦を指定した一部の年で日曜日以外の日付を返しました。メンテナーは異なるカレンダー体系による意図した挙動だと判断し、issue(#1437)は無効(invalid)として閉じられています。メンテナーはこの意味づけが微妙であることも認めています。コードが暗黙の前提を置いている場合、何を正しい性質とするかを決められるのはライブラリのメンテナーだけです。微妙な意味論を持つコードから性質を導くのは、このエージェントにとって依然として難しい領域です。
5件のうち3件はマージ済み、1件はパッチ提出済み、1件は無効でした。論文の要旨では、報告した5件のうち4件にパッチがあり、3件がマージされたとまとめられています。
パッチの自動生成はあえて対象外にしている
バグの自動修正そのものは今回の研究の対象外です。5件の代表バグに添えたパッチ案は研究者が付けたもので、waldについては研究者が原因をたどって再定式化しています。エージェントによるパッチの自動生成は今回の対象外です。
研究チームはこの点を今後の課題としています。コードのある部分が満たすべき正しさの性質を(ほぼ)完全に指定できれば、バグの修正自体はずっと容易になるという見立てです。近い将来、LLMがメンテナーの検討に値する高品質なパッチを提案できるようになるとも述べていますが、これは今回のエージェントが示した能力ではなく、今後の研究方向として語られているものです。
この研究がテスト運用に示すもの
研究チームは、エージェント型のProperty-Based Testingが人間の書くテストを補完する存在として、ますます価値を持つ可能性があるとしています。関数名・docstring・呼び出され方といった文脈から「本来成り立つべき性質」を言語モデルが読み取れる点が、この手法との相性を支えているという見方です。E2Eテストの失敗診断にも同種のMCPがあり、Cypress CloudのMCPをClaude Codeで使うがその例です。
今回の実験が狙ったのはセキュリティ脆弱性に限らない一般的なロジックバグです。ただ、脆弱性の原因になるロジックバグの種類の多くはこの手法に向いており、研究チームは別のブログ記事でスマートコントラクトのバグ探索にも触れています。脆弱性の悪用に長けたLLMが増えるほど、攻撃者に先んじてLLMでテストとバグ探索を進める必要がある、というのが研究チームの指摘です。デプロイ前にバグを先回りして見つける使い方も、将来の可能性として挙げています。
まとめ
件数の多さより、生成されたレポートを人手で絞り込む工程が、この手法の運用コストと信頼性を決めています。自分のコードに当てるなら、docstringや型注釈に性質の手がかりをどれだけ書き込んであるかが、エージェントの当たり外れを左右します。
Claude Codeでこうしたエージェント的なワークフローを組む土台は、サブエージェントの並列設計にも共通します。Anthropicの研究チームが自律型エージェントで長時間タスクをやり切った別の事例は、フェルマーの最終定理の形式化でも読めます。