Claude Codeが書いたSwiftのテスト、本当にバグを検出できる? Mutation Testingで穴を埋める
Claude CodeやCodexのようなCoding Agentを使うと、実装からテスト追加、修正までかなりの範囲を自動化できるようになりました。
例えば、こんな指示を出します。
この機能を実装してください。
既存テストを壊さず、必要なテストも追加してください。
Agentはコードを書き、テストを追加し、最後にこう報告します。
All tests passed.
これで仕事は終わっているように見えます。しかし、「テストがすべて通った」ことと、「そのテストが十分にバグを検出できる」ことは同じではありません。
AIにコードを書かせる比率が増えるほど、この違いは重要になると考えています。
そこで私は、Swift / Xcodeのプロジェクトで、Mutation Testingでtest suiteの穴を見つけ、Coding Agentにそれを埋めさせる仕組みを試しています。
greenなテストは、何を保証しているのか
通常のテストは、次のように実装に対して実行します。
implementation → tests → green
テストが落ちれば問題は分かります。難しいのは、テストが通った場合です。greenから分かるのは、実行されたテストが確認している条件を満たしたことだけです。確認していない条件については、何も分かりません。
例えば次のコードがあったとします。
func canPurchase(age: Int) -> Bool {
age >= 18
}
テストが、
@Test func adultCanPurchase() {
#expect(canPurchase(age: 20))
}
だけだったとしても、テストはgreenです。
しかし、age >= 18 を age > 18 に変えても、このテストは通ります。18歳という境界条件がテストされていないからです。それどころか、関数の中身を true に置き換えても通ります。false になるケースを1つも確認していないためです。
Line Coverageが100%だったとしても、この問題は検出できません。
「その行を通ったか」ではなく、「そのコードの意味が変わったとき、テストは気付けるか」 を確認する必要があります。
Mutation Testing
Mutation Testingでは、プログラムに意図的に小さな変更を加えます。先ほどの age >= 18 なら、例えば age > 18 に変更します。この変更を「mutation」、変更を加えたプログラムを「mutant」と呼びます。
その状態でテストを実行し、
テストが失敗した → killed
テストが全部通った → survived
と判定します。survivedしたmutant(以下、survivor)は、この変更が入ってもテストは異常を検出できなかった ことを意味します。
つまりMutation Testingは、テストコードそのものをテストする ための仕組みとも言えます。
なお、ここでは説明のために2値にしていますが、MutantKitでは、mutationを適用できなかった場合や実行環境の問題なども区別します。これは後で触れる、結果の信頼性の話につながります。
Coding Agentと組み合わせる
Mutation Testingには、実務上の問題があります。survivorを見つけた後の作業が人手に頼っていたことです。人間が、
差分を見る
なぜ生き残ったのか考える
必要なテストを考える
テストを書く
再実行する
必要があり、これはそれなりに面倒です。
Coding Agentなら、このループ自体をかなり自動化できます。
実装
↓
通常テスト → green
↓
mutation testing
↓
survivorを発見
↓
Agentがsurvivorを分析
├─ 仕様上必要な振る舞いが未検証 → テストを追加 → mutation testingを再実行
└─ 仕様上重要でない変更 → テストは足さず、理由を説明する
先ほどの canPurchase なら、>= → > のsurvivorが出て、Agentが「18歳の境界が未検証」と分析し、次のテストを追加します。
@Test func eighteenCanPurchase() {
#expect(canPurchase(age: 18))
}
@Test func seventeenCannotPurchase() {
#expect(!canPurchase(age: 17))
}
再実行すると、先ほどのmutantはkilledになります。
ここで重要なのは役割の分担です。変更を加えてテストを走らせるのはツールで、Agentは生き残った変更から、足りないテストを考えて追加する役です。
Agentに「どこを変えるか」まで任せると、実装時の思い込みがそのまま検証にも入り込みます。変更の入れ方をツールが機械的に決めれば、Agentが見落としていた境界条件も突かれます。
Agentには「survivorを0にしろ」と言わない
Mutation TestingをAgentに渡すと、「survivorを全部消してください」と言いたくなります。しかし、この指示はあまり良くありません。
Agentは数字を改善するためだけのテストを書き始める可能性があります。例えば実装の内部構造に過度に依存したテストや、そのmutantだけを狙い撃ちする不自然なテストです。
Mutation Score 100%自体を目的にすべきではありません。私はむしろ、次のように指示しています。
このsurvivorが、仕様上必要な振る舞いの検証不足を示しているか分析してください。
検証不足なら、公開された振る舞いを検証するテストを追加してください。
仕様上重要でない変更なら、その理由を説明してください。
Mutation Testingはテストを機械的に増やすツールというより、Agentに「このテスト、本当に意味ある?」と問い続けるためのフィードバックとして使う方がよいと思います。
MutantKit
このワークフローをSwiftで試すために作っているのが、MutantKitです。Swift / Xcode向けのMutation Testingツールで、要件はmacOS 14+、Apple Silicon、Swift 6.0+です。
juntaki/mutantkit
基本的には次の流れで実行します。
brew install juntaki/mutantkit/mutantkit
mutantkit setup # プロジェクト・scheme・テストターゲットを検出し、設定ファイルを生成
mutantkit dry-run # mutationなしで、通常のビルドとテストが通るか確認
mutantkit plan --output plan.json # 適用するmutationの一覧を作る
mutantkit run --plan plan.json # mutationを適用し、テストが検出できるか確認
CIでsurvivorがあれば失敗させたい場合は、run に --fail-on-survivors を付けます。
Swiftには、すでにMuterというMutation Testingツールがあります。他の言語にもPIT(Java)、Stryker(JavaScript / TypeScript など)、mutmut(Python)があります。MutantKitで特に力を入れているのは、mutationを大量に生成することよりも、そのmutantが本当にテストされたのかを確認することです。
Agentに渡すsurvivorは本物でなければならない
Agentはsurvivorをtest suiteの穴として受け取り、それを埋めるテストを書きにいきます。もしsurvivorの中に、実は環境の問題やmutationの適用失敗で「通ってしまった」だけのものが混ざっていると、Agentは存在しない穴を埋めようとして、意味のないテストを書くことになります。
Mutation Testingを実装してみて分かったのですが、ソースコードを書き換えただけでは、そのmutantが実際にテストされたとは限りません。間にはいくつもの段階があります。
source mutation
↓
build
↓
compiled binary(変更前と違うか)
↓
test execution
↓
mutated codeを実際に通る
↓
test result
この途中では、例えば次のようなことが起きます。
plan作成後にソースが変わり、mutationを安全に適用できなかった
ソースを書き換えてビルドが通ったのに、変更がビルド成果物に入らなかった(条件付きコンパイルで外れたコードや、どこからも使われずにビルドから取り除かれたコードを変えた場合)
mutationを入れる前のテストが、事前の確認時と違う結果になった
Simulatorやテスト実行環境が壊れた
どれも、テストが変更を確かめたとは言えない状態です。これを無視して、テストが落ちたらkilled、通ったらsurvivedとしてしまうと、結果の意味が曖昧になります。
また、mutationした行をどのテストも実行していないこともあります。これは通常のカバレッジ測定でも分かるテストの穴で、上の4つとは違って結果としては有用です。ただ、「実行はされたのに検出できなかった」mutantとは意味が違うので、survivedとは分けておきます。
そこでMutantKitでは、結果を細かく分けています。主なものは次のとおりです(ほかにも、コンパイルできないmutantなどを区別します)。
結果
意味
killed
いずれかのテストが失敗し、mutationを検出した
survived
mutationが成果物に反映されたが、テストは全て通った
noCoverage
mutationされたコードをどのテストも実行しなかった
notApplied
mutationを安全に適用できなかった
notMaterialized
mutationは適用されたが、成果物が変更前と同じだった
baselineMismatch
mutationなしのベースラインが、記録時と同じ結果にならなかった
infrastructureFailure
実行環境そのものに問題があった
survivedと判定するのは、mutationが適用され、ビルド成果物が変更前と違うことを確認できた場合だけです。ただし、mutationしたコードがテストで実際に実行されたかまでは、網羅率にもとづくテスト選択(selectCoveringTests)を有効にしたときだけ確認します。有効にすると、どのテストも実行しないmutantはnoCoverageに分かれ、survivedは「実行されたのに検出できなかった」mutantだけになります。無効のままだと、両方がsurvivedにまとめて数えられます。
AgentにはsurvivedとnoCoverageを分析させます。一方、notAppliedやnotMaterializedなど、正常に検証できなかった結果は、テスト改善の対象とはせず、まず原因を調べます。
スコアは2種類あります。
Tested: killed / (killed + survived)。selectCoveringTestsを有効にした場合は、テストで実際に実行されたmutantに対する検出率になります
Effective: killed / (killed + survived + noCoverage)。テストが一度も実行しない箇所も含めた検出率
noCoverageはテストの穴なので、Effectiveには含めます。selectCoveringTestsが無効だとnoCoverageは0件になるので、TestedとEffectiveは同じ値になります。一方でnotMaterialized、notApplied、baselineMismatch、infrastructureFailureは、スコアには混ぜず、別に報告します。
どちらのスコアも、今回選択して実行したmutantの結果から算出します。対象ファイル、operator、budgetによって値が変わるので、プロジェクト全体のバグ検出能力を直接表すものではありません。比較するときは、これらの条件を揃える必要があります。
Claude Codeから使う
MutantKitにはCoding Agent向けのskillも用意しています。Claude Codeなら、次のコマンドでpluginを入れます。
brew install juntaki/mutantkit/mutantkit
claude plugin marketplace add juntaki/mutantkit
claude plugin install mutantkit@mutantkit
あとは対象プロジェクトのルートでClaude Codeを起動し、こう指示します。
MutantKitでこのプロジェクトのテストの穴を探して、必要なテストを追加してください。
すると、setup → dry-run → plan → run → survivor analysis → test improvement という流れをAgent側で進められます。小さいbudgetで始めることや、実行の不備がないかを確かめてからsurvivedとnoCoverageを分析することは、skillに手順として書いてあります。Codex用のpluginも同じskillを使っています。
mutationを一度実行して終わるのではなく、開発ループの中に入れることが狙いです。
実行時間とコスト
Mutation Testingには、もう1つ実務上の問題があります。実行時間です。単純な方式ではmutantごとにビルドとテストが必要になり、規模が大きいほど時間がかかります。Xcodeプロジェクトではなおさらです。
MutantKitでは、次のような手段で実行時間を抑えます。
mutation budget: 1回の実行で試すmutantの上限数です。設定のexecution.budget.maxMutantsで指定します。最初は20〜50件程度の小さな値で試すのがお勧めです。dry-runで見つからない問題があっても、被害を小さく抑えられます。
coverage-based test selection: 各mutationのコードを実行するテストだけを実行します。設定のselectCoveringTestsで有効にします。最初に各テストの網羅率を取る時間がかかりますが、noCoverageの判定にも必要なので、有効にしておくのがお勧めです。
planのsharding: planを複数のworkerに分割して並列実行できます。中断した実行を再開することもできます。
Agentに任せる場合は、トークンコストもかかります。budgetで分析対象のmutantの数は抑えられますが、Agentの再試行回数や総トークン量は別に管理する必要があります。
実際のプロジェクトで動かした結果
swift-argument-parser
swift-argument-parserに対して、budgetを50件、selectCoveringTestsを有効にして実行した結果です。MutantKitはv1.3.1を使っています。
項目
値
生成候補のmutant
613件
実行したmutant(budget)
50件
killed
31件
survived
5件
noCoverage
12件
除外(コンパイルできない)
2件
Tested
31 / 36 ≒ 86%
Effective
31 / 48 ≒ 65%
所要時間
約6分
Testedは86%ですが、Effectiveは65%です。この差は、50件中12件が、どのテストも実行しない行だったことによるものです。実行されるコードに対してはテストがよく効いていても、テストが届いていない領域が残っていることが分かります。
この5件と12件が、Agentに分析させる対象になります。
MutantKit自身
survivorを分析すると何が見えるのか。同じv1.3.1を、MutantKit自身のソースに実行して確かめました。条件はswift-argument-parserと同じです。
項目
値
生成候補のmutant
4072件
実行したmutant(budget)
50件
killed
21件
survived
16件
noCoverage
10件
除外(実行環境の問題など)
3件
Tested
21 / 37 ≒ 57%
Effective
21 / 47 ≒ 45%
所要時間
約76分
MutantKitには約2,900件のテストがあり、普段は毎回greenです。それでも50件中16件がsurvived、10件がnoCoverageでした。今回の50件はXcode連携の部分に偏っていて、Xcodeやシミュレータを使う受け入れテストは既定の実行から外れるため、プロジェクト全体の検出率を表すものではありません。
除外した3件のうち2件は、実際にはテストが検出していたのに、MutantKit側の不具合で判定できなかったものです。判定できない結果をsurvivedに混ぜず除外する設計のおかげで、誤ったsurvivorとしてAgentに渡らずに済んでいます。
survivedの16件をClaude Codeに分析させ、テストの追加まで進めました。
区分
件数
対応
意味が変わらない(等価)と思われるもの
9件
テストは足さない
テストの穴
2件
テストを追加し、killedにした
テストの穴だが、そのままではテストにできない
5件
設計の変更が必要
等価なものの例です。
// 元のコード
.sorted { $0.0 < $1.0 }
// mutant
.sorted { $0.0 <= $1.0 }
キーが重複しないので、<を<=に変えてもソート結果は変わりません。ほかに、var isDirectory: ObjCBool = falseをtrueにするmutantもありました。直後に値が上書きされるので、初期値は結果に影響しません。
等価なmutantは、どんなテストを書いてもkillできません。「survivorを0にしろ」とAgentに言わない理由が、ここにあります。0を目標にすると、Agentはこの9件を無理にkillしようとして、不自然なテストを書くか、実装そのものを書き換えようとします。
テストを足してkilledにしたものの1つは、Xcodeのworkspaceファイル(XML)で入れ子のグループを読む処理で、グループの終わりを判定する==を!=にするmutantです。既存のテストはグループ内のファイルが1つだけのケースしかなく、間違った実装でも通っていました。2つ目以降のファイルも正しいグループの下に入ることを確かめるテストを足し、killedにしました。
テストにできなかった5件は、どれもxcodebuildやswift testを直接呼ぶ処理で、テストから差し替える入口がありませんでした。
テストを「通す」Agentから、テストを「疑う」Agentへ
Coding Agentについては、「AIにテストも書かせればよい」という話がよくあります。もちろん、それ自体は有効です。
ただし、コードを書いたAgentと同じAgentがテストも書くと、実装時の思い込みがテストにも入り込みます。人間でも同じことは起きます。Mutation Testingは、そこに逆方向からの確認を加えます。Agentの判断に依存しない機械的なmutantで実装を少し壊し、この変更をあなたのテストは検出できますか? と問う、adversarial validationです。
ただし、万能ではありません。実装とテストが共有している仕様の誤解までは検出できません。追加するテストの期待値は、仕様や受け入れ条件に照らして人間が確認する必要があります。
AIによってコードを書くコストは急速に下がっています。同時に、
コードが生成できた
テストも生成できた
CIもgreen
という状態まで到達するコストも下がっています。だからこそ、次に重要になるのは、そのgreenをどこまで信用してよいかだと思います。
Coding Agentにコードを書かせる。
Coding Agentにテストを書かせる。
そして最後に、Mutation Testingでそのtest suiteの穴を洗い出し、Coding Agentに向き合わせる。
Mutation Testingは以前から存在する技術ですが、Coding Agentと組み合わせることで、実用的な開発ループになるのではないかと考えています。MutantKitでは、この方向をもう少し掘っていこうと思っています。
1 like
G: Discover Swift Argument Parser: straightforward, type-safe command-line parsing. See a complete example with @main ParsableCommand, flags, options, and arguments, plus docs and real-world usage in swift-format and swift-package-manager. Read more: apple/swift-argument-parser
5 views
1: Swift Argument Parser offers straightforward, type-safe CLI argument parsing for Swift. Define a ParsableCommand with property wrappers, implement run(), and rely on helpful errors and usage messages. See documentation for details. apple/swift-argument-parser
3: Swift Argument Parser makes CLI tools easy with straightforward, type-safe argument parsing and automatic help/error messages. Define a ParsableCommand, decorate properties, and build polished CLIs. apple/swift-argument-parser apple/swift-argument-parser
2 views
2: Swift Argument Parser offers straightforward, type-safe CLI parsing. Define a ParsableCommand with property wrappers (@Flag, @Option, @Argument), auto-generated help, and a run() method. Easy to add via SwiftPM; supports async commands and multiple Sw… apple/swift-argument-parser
1 view
Swift Argument Parser: straightforward, type-safe argument parsing for Swift (3.8k stars)
What it does
All text below is quoted directly from the project README.
>Straightforward, type-safe argument parsing for Swift
Examples:
- repeat is the example shown above.
- roll is a simple utility implemented as a straight-line script.
- math is an annotated example of using nested commands and subcommands.
- count-lines uses async/await code in its implementation.
- default-as-flag demonstrates hybrid options that can work both as flags and as options with values.
>The ArgumentParser library parses the command-line arguments, instantiates your command type, and then either executes your run() method or exits with a useful message.
Repository
apple/swift-argument-parser
Repository stats
|Metric|Value|
|:-|:-|
|Stars|3,793|
|Forks|438|
|Last release|1.8.2 (2026-06-04)|
|Languages|Swift 90.4%, Shell 7.2%, Roff 1.5%|
|Contributors|122|
|AI contributor accounts (Claude / Codex / Cursor / Copilot)|None|
|AI co-authored commits|None|
Main contributor
|Field|Value|
|:-|:-|
|Name|Nate Cook|
|GitHub|@natecook1000|
|Commits to this repo|204 (top contributor)|
|Company / Location|Evanston, IL|
|Followers|870|
──────────────────────────────
📌 How many great GitHub repos have you discovered and completely forgotten about?
Don't let another useful tool disappear into your Reddit feed. Get the community's best repos, organized by category, plus key takeaways from real developer discussions, delivered to your inbox twice a week.
📬 Never lose track of great repos again. Subscribe free.
2 points
Mentions
Claude Codeが書いたSwiftのテスト、本当にバグを検出できる? Mutation Testingで穴を埋める Claude CodeやCodexのようなCoding Agentを使うと、実装からテスト追加、修正までかなりの範囲を自動化できるようになりました。 例えば、こんな指示を出します。 この機能を実装してください。 既存テストを壊さず、必要なテストも追加してください。 Agentはコードを書き、テストを追加し、最後にこう報告します。 All tests passed. これで仕事は終わっているように見えます。しかし、「テストがすべて通った」ことと、「そのテストが十分にバグを検出できる」ことは同じではありません。 AIにコードを書かせる比率が増えるほど、この違いは重要になると考えています。 そこで私は、Swift / Xcodeのプロジェクトで、Mutation Testingでtest suiteの穴を見つけ、Coding Agentにそれを埋めさせる仕組みを試しています。 greenなテストは、何を保証しているのか 通常のテストは、次のように実装に対して実行します。 implementation → tests → green テストが落ちれば問題は分かります。難しいのは、テストが通った場合です。greenから分かるのは、実行されたテストが確認している条件を満たしたことだけです。確認していない条件については、何も分かりません。 例えば次のコードがあったとします。 func canPurchase(age: Int) -> Bool { age >= 18 } テストが、 @Test func adultCanPurchase() { #expect(canPurchase(age: 20)) } だけだったとしても、テストはgreenです。 しかし、age >= 18 を age > 18 に変えても、このテストは通ります。18歳という境界条件がテストされていないからです。それどころか、関数の中身を true に置き換えても通ります。false になるケースを1つも確認していないためです。 Line Coverageが100%だったとしても、この問題は検出できません。 「その行を通ったか」ではなく、「そのコードの意味が変わったとき、テストは気付けるか」 を確認する必要があります。 Mutation Testing Mutation Testingでは、プログラムに意図的に小さな変更を加えます。先ほどの age >= 18 なら、例えば age > 18 に変更します。この変更を「mutation」、変更を加えたプログラムを「mutant」と呼びます。 その状態でテストを実行し、 テストが失敗した → killed テストが全部通った → survived と判定します。survivedしたmutant(以下、survivor)は、この変更が入ってもテストは異常を検出できなかった ことを意味します。 つまりMutation Testingは、テストコードそのものをテストする ための仕組みとも言えます。 なお、ここでは説明のために2値にしていますが、MutantKitでは、mutationを適用できなかった場合や実行環境の問題なども区別します。これは後で触れる、結果の信頼性の話につながります。 Coding Agentと組み合わせる Mutation Testingには、実務上の問題があります。survivorを見つけた後の作業が人手に頼っていたことです。人間が、 差分を見る なぜ生き残ったのか考える 必要なテストを考える テストを書く 再実行する 必要があり、これはそれなりに面倒です。 Coding Agentなら、このループ自体をかなり自動化できます。 実装 ↓ 通常テスト → green ↓ mutation testing ↓ survivorを発見 ↓ Agentがsurvivorを分析 ├─ 仕様上必要な振る舞いが未検証 → テストを追加 → mutation testingを再実行 └─ 仕様上重要でない変更 → テストは足さず、理由を説明する 先ほどの canPurchase なら、>= → > のsurvivorが出て、Agentが「18歳の境界が未検証」と分析し、次のテストを追加します。 @Test func eighteenCanPurchase() { #expect(canPurchase(age: 18)) } @Test func seventeenCannotPurchase() { #expect(!canPurchase(age: 17)) } 再実行すると、先ほどのmutantはkilledになります。 ここで重要なのは役割の分担です。変更を加えてテストを走らせるのはツールで、Agentは生き残った変更から、足りないテストを考えて追加する役です。 Agentに「どこを変えるか」まで任せると、実装時の思い込みがそのまま検証にも入り込みます。変更の入れ方をツールが機械的に決めれば、Agentが見落としていた境界条件も突かれます。 Agentには「survivorを0にしろ」と言わない Mutation TestingをAgentに渡すと、「survivorを全部消してください」と言いたくなります。しかし、この指示はあまり良くありません。 Agentは数字を改善するためだけのテストを書き始める可能性があります。例えば実装の内部構造に過度に依存したテストや、そのmutantだけを狙い撃ちする不自然なテストです。 Mutation Score 100%自体を目的にすべきではありません。私はむしろ、次のように指示しています。 このsurvivorが、仕様上必要な振る舞いの検証不足を示しているか分析してください。 検証不足なら、公開された振る舞いを検証するテストを追加してください。 仕様上重要でない変更なら、その理由を説明してください。 Mutation Testingはテストを機械的に増やすツールというより、Agentに「このテスト、本当に意味ある?」と問い続けるためのフィードバックとして使う方がよいと思います。 MutantKit このワークフローをSwiftで試すために作っているのが、MutantKitです。Swift / Xcode向けのMutation Testingツールで、要件はmacOS 14+、Apple Silicon、Swift 6.0+です。 juntaki/mutantkit 基本的には次の流れで実行します。 brew install juntaki/mutantkit/mutantkit mutantkit setup # プロジェクト・scheme・テストターゲットを検出し、設定ファイルを生成 mutantkit dry-run # mutationなしで、通常のビルドとテストが通るか確認 mutantkit plan --output plan.json # 適用するmutationの一覧を作る mutantkit run --plan plan.json # mutationを適用し、テストが検出できるか確認 CIでsurvivorがあれば失敗させたい場合は、run に --fail-on-survivors を付けます。 Swiftには、すでにMuterというMutation Testingツールがあります。他の言語にもPIT(Java)、Stryker(JavaScript / TypeScript など)、mutmut(Python)があります。MutantKitで特に力を入れているのは、mutationを大量に生成することよりも、そのmutantが本当にテストされたのかを確認することです。 Agentに渡すsurvivorは本物でなければならない Agentはsurvivorをtest suiteの穴として受け取り、それを埋めるテストを書きにいきます。もしsurvivorの中に、実は環境の問題やmutationの適用失敗で「通ってしまった」だけのものが混ざっていると、Agentは存在しない穴を埋めようとして、意味のないテストを書くことになります。 Mutation Testingを実装してみて分かったのですが、ソースコードを書き換えただけでは、そのmutantが実際にテストされたとは限りません。間にはいくつもの段階があります。 source mutation ↓ build ↓ compiled binary(変更前と違うか) ↓ test execution ↓ mutated codeを実際に通る ↓ test result この途中では、例えば次のようなことが起きます。 plan作成後にソースが変わり、mutationを安全に適用できなかった ソースを書き換えてビルドが通ったのに、変更がビルド成果物に入らなかった(条件付きコンパイルで外れたコードや、どこからも使われずにビルドから取り除かれたコードを変えた場合) mutationを入れる前のテストが、事前の確認時と違う結果になった Simulatorやテスト実行環境が壊れた どれも、テストが変更を確かめたとは言えない状態です。これを無視して、テストが落ちたらkilled、通ったらsurvivedとしてしまうと、結果の意味が曖昧になります。 また、mutationした行をどのテストも実行していないこともあります。これは通常のカバレッジ測定でも分かるテストの穴で、上の4つとは違って結果としては有用です。ただ、「実行はされたのに検出できなかった」mutantとは意味が違うので、survivedとは分けておきます。 そこでMutantKitでは、結果を細かく分けています。主なものは次のとおりです(ほかにも、コンパイルできないmutantなどを区別します)。 結果 意味 killed いずれかのテストが失敗し、mutationを検出した survived mutationが成果物に反映されたが、テストは全て通った noCoverage mutationされたコードをどのテストも実行しなかった notApplied mutationを安全に適用できなかった notMaterialized mutationは適用されたが、成果物が変更前と同じだった baselineMismatch mutationなしのベースラインが、記録時と同じ結果にならなかった infrastructureFailure 実行環境そのものに問題があった survivedと判定するのは、mutationが適用され、ビルド成果物が変更前と違うことを確認できた場合だけです。ただし、mutationしたコードがテストで実際に実行されたかまでは、網羅率にもとづくテスト選択(selectCoveringTests)を有効にしたときだけ確認します。有効にすると、どのテストも実行しないmutantはnoCoverageに分かれ、survivedは「実行されたのに検出できなかった」mutantだけになります。無効のままだと、両方がsurvivedにまとめて数えられます。 AgentにはsurvivedとnoCoverageを分析させます。一方、notAppliedやnotMaterializedなど、正常に検証できなかった結果は、テスト改善の対象とはせず、まず原因を調べます。 スコアは2種類あります。 Tested: killed / (killed + survived)。selectCoveringTestsを有効にした場合は、テストで実際に実行されたmutantに対する検出率になります Effective: killed / (killed + survived + noCoverage)。テストが一度も実行しない箇所も含めた検出率 noCoverageはテストの穴なので、Effectiveには含めます。selectCoveringTestsが無効だとnoCoverageは0件になるので、TestedとEffectiveは同じ値になります。一方でnotMaterialized、notApplied、baselineMismatch、infrastructureFailureは、スコアには混ぜず、別に報告します。 どちらのスコアも、今回選択して実行したmutantの結果から算出します。対象ファイル、operator、budgetによって値が変わるので、プロジェクト全体のバグ検出能力を直接表すものではありません。比較するときは、これらの条件を揃える必要があります。 Claude Codeから使う MutantKitにはCoding Agent向けのskillも用意しています。Claude Codeなら、次のコマンドでpluginを入れます。 brew install juntaki/mutantkit/mutantkit claude plugin marketplace add juntaki/mutantkit claude plugin install mutantkit@mutantkit あとは対象プロジェクトのルートでClaude Codeを起動し、こう指示します。 MutantKitでこのプロジェクトのテストの穴を探して、必要なテストを追加してください。 すると、setup → dry-run → plan → run → survivor analysis → test improvement という流れをAgent側で進められます。小さいbudgetで始めることや、実行の不備がないかを確かめてからsurvivedとnoCoverageを分析することは、skillに手順として書いてあります。Codex用のpluginも同じskillを使っています。 mutationを一度実行して終わるのではなく、開発ループの中に入れることが狙いです。 実行時間とコスト Mutation Testingには、もう1つ実務上の問題があります。実行時間です。単純な方式ではmutantごとにビルドとテストが必要になり、規模が大きいほど時間がかかります。Xcodeプロジェクトではなおさらです。 MutantKitでは、次のような手段で実行時間を抑えます。 mutation budget: 1回の実行で試すmutantの上限数です。設定のexecution.budget.maxMutantsで指定します。最初は20〜50件程度の小さな値で試すのがお勧めです。dry-runで見つからない問題があっても、被害を小さく抑えられます。 coverage-based test selection: 各mutationのコードを実行するテストだけを実行します。設定のselectCoveringTestsで有効にします。最初に各テストの網羅率を取る時間がかかりますが、noCoverageの判定にも必要なので、有効にしておくのがお勧めです。 planのsharding: planを複数のworkerに分割して並列実行できます。中断した実行を再開することもできます。 Agentに任せる場合は、トークンコストもかかります。budgetで分析対象のmutantの数は抑えられますが、Agentの再試行回数や総トークン量は別に管理する必要があります。 実際のプロジェクトで動かした結果 swift-argument-parser swift-argument-parserに対して、budgetを50件、selectCoveringTestsを有効にして実行した結果です。MutantKitはv1.3.1を使っています。 項目 値 生成候補のmutant 613件 実行したmutant(budget) 50件 killed 31件 survived 5件 noCoverage 12件 除外(コンパイルできない) 2件 Tested 31 / 36 ≒ 86% Effective 31 / 48 ≒ 65% 所要時間 約6分 Testedは86%ですが、Effectiveは65%です。この差は、50件中12件が、どのテストも実行しない行だったことによるものです。実行されるコードに対してはテストがよく効いていても、テストが届いていない領域が残っていることが分かります。 この5件と12件が、Agentに分析させる対象になります。 MutantKit自身 survivorを分析すると何が見えるのか。同じv1.3.1を、MutantKit自身のソースに実行して確かめました。条件はswift-argument-parserと同じです。 項目 値 生成候補のmutant 4072件 実行したmutant(budget) 50件 killed 21件 survived 16件 noCoverage 10件 除外(実行環境の問題など) 3件 Tested 21 / 37 ≒ 57% Effective 21 / 47 ≒ 45% 所要時間 約76分 MutantKitには約2,900件のテストがあり、普段は毎回greenです。それでも50件中16件がsurvived、10件がnoCoverageでした。今回の50件はXcode連携の部分に偏っていて、Xcodeやシミュレータを使う受け入れテストは既定の実行から外れるため、プロジェクト全体の検出率を表すものではありません。 除外した3件のうち2件は、実際にはテストが検出していたのに、MutantKit側の不具合で判定できなかったものです。判定できない結果をsurvivedに混ぜず除外する設計のおかげで、誤ったsurvivorとしてAgentに渡らずに済んでいます。 survivedの16件をClaude Codeに分析させ、テストの追加まで進めました。 区分 件数 対応 意味が変わらない(等価)と思われるもの 9件 テストは足さない テストの穴 2件 テストを追加し、killedにした テストの穴だが、そのままではテストにできない 5件 設計の変更が必要 等価なものの例です。 // 元のコード .sorted { $0.0 < $1.0 } // mutant .sorted { $0.0 <= $1.0 } キーが重複しないので、<を<=に変えてもソート結果は変わりません。ほかに、var isDirectory: ObjCBool = falseをtrueにするmutantもありました。直後に値が上書きされるので、初期値は結果に影響しません。 等価なmutantは、どんなテストを書いてもkillできません。「survivorを0にしろ」とAgentに言わない理由が、ここにあります。0を目標にすると、Agentはこの9件を無理にkillしようとして、不自然なテストを書くか、実装そのものを書き換えようとします。 テストを足してkilledにしたものの1つは、Xcodeのworkspaceファイル(XML)で入れ子のグループを読む処理で、グループの終わりを判定する==を!=にするmutantです。既存のテストはグループ内のファイルが1つだけのケースしかなく、間違った実装でも通っていました。2つ目以降のファイルも正しいグループの下に入ることを確かめるテストを足し、killedにしました。 テストにできなかった5件は、どれもxcodebuildやswift testを直接呼ぶ処理で、テストから差し替える入口がありませんでした。 テストを「通す」Agentから、テストを「疑う」Agentへ Coding Agentについては、「AIにテストも書かせればよい」という話がよくあります。もちろん、それ自体は有効です。 ただし、コードを書いたAgentと同じAgentがテストも書くと、実装時の思い込みがテストにも入り込みます。人間でも同じことは起きます。Mutation Testingは、そこに逆方向からの確認を加えます。Agentの判断に依存しない機械的なmutantで実装を少し壊し、この変更をあなたのテストは検出できますか? と問う、adversarial validationです。 ただし、万能ではありません。実装とテストが共有している仕様の誤解までは検出できません。追加するテストの期待値は、仕様や受け入れ条件に照らして人間が確認する必要があります。 AIによってコードを書くコストは急速に下がっています。同時に、 コードが生成できた テストも生成できた CIもgreen という状態まで到達するコストも下がっています。だからこそ、次に重要になるのは、そのgreenをどこまで信用してよいかだと思います。 Coding Agentにコードを書かせる。 Coding Agentにテストを書かせる。 そして最後に、Mutation Testingでそのtest suiteの穴を洗い出し、Coding Agentに向き合わせる。 Mutation Testingは以前から存在する技術ですが、Coding Agentと組み合わせることで、実用的な開発ループになるのではないかと考えています。MutantKitでは、この方向をもう少し掘っていこうと思っています。
G: Discover Swift Argument Parser: straightforward, type-safe command-line parsing. See a complete example with @main ParsableCommand, flags, options, and arguments, plus docs and real-world usage in swift-format and swift-package-manager. Read more: apple/swift-argument-parser
1: Swift Argument Parser offers straightforward, type-safe CLI argument parsing for Swift. Define a ParsableCommand with property wrappers, implement run(), and rely on helpful errors and usage messages. See documentation for details. apple/swift-argument-parser
3: Swift Argument Parser makes CLI tools easy with straightforward, type-safe argument parsing and automatic help/error messages. Define a ParsableCommand, decorate properties, and build polished CLIs. apple/swift-argument-parser apple/swift-argument-parser
2: Swift Argument Parser offers straightforward, type-safe CLI parsing. Define a ParsableCommand with property wrappers (@Flag, @Option, @Argument), auto-generated help, and a run() method. Easy to add via SwiftPM; supports async commands and multiple Sw… apple/swift-argument-parser
Swift Argument Parser: straightforward, type-safe argument parsing for Swift (3.8k stars) What it does All text below is quoted directly from the project README. >Straightforward, type-safe argument parsing for Swift Examples: - repeat is the example shown above. - roll is a simple utility implemented as a straight-line script. - math is an annotated example of using nested commands and subcommands. - count-lines uses async/await code in its implementation. - default-as-flag demonstrates hybrid options that can work both as flags and as options with values. >The ArgumentParser library parses the command-line arguments, instantiates your command type, and then either executes your run() method or exits with a useful message. Repository apple/swift-argument-parser Repository stats |Metric|Value| |:-|:-| |Stars|3,793| |Forks|438| |Last release|1.8.2 (2026-06-04)| |Languages|Swift 90.4%, Shell 7.2%, Roff 1.5%| |Contributors|122| |AI contributor accounts (Claude / Codex / Cursor / Copilot)|None| |AI co-authored commits|None| Main contributor |Field|Value| |:-|:-| |Name|Nate Cook| |GitHub|@natecook1000| |Commits to this repo|204 (top contributor)| |Company / Location|Evanston, IL| |Followers|870| ────────────────────────────── 📌 How many great GitHub repos have you discovered and completely forgotten about? Don't let another useful tool disappear into your Reddit feed. Get the community's best repos, organized by category, plus key takeaways from real developer discussions, delivered to your inbox twice a week. 📬 Never lose track of great repos again. Subscribe free.