mutation-testing について
mutation-testing は、テストの有効性を評価・改善するための実践ガイドです。テストが不具合を検出できるか確認したい開発者向けに、ミューテーション分析、残存ミュータント、Stryker、エージェント駆動の手順を扱います。
主な機能
- 01コード変異によるテスト検証
- 02ミューテーションスコアの算出
- 03残存ミュータントの特定
- 04Stryker の設定と実行
- 05エージェント駆動分析への対応
活用シーン
- 01テストスイートの品質評価
- 02アサーションの不足箇所の発見
- 03リリース前の重要経路の確認
- 04残存ミュータント向けテストの追加
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ proffesor-for-testing/ agentic-qe --skill mutation-testingインストール後、Claude Code で/mutation-testingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
config.jsonrun-history.jsonevals/mutation-testing.yamlreferences/mutation-operators.mdschemas/output.jsonscripts/validate-config.jsontest-data/sample-output.json詳細
ミューテーションテスト
<default_to_action> テスト品質を検証したり、テストの有効性を高めたりする場合:
- コードをミューテーションします(+ を - に変更する、>= を > に変更する、文を削除する)
- 各ミュータントに対してテストを実行します
- テストがミューテーションを検出することを確認します(ミュータントをキルする)
- 生き残ったミュータントを特定します(テストの改善が必要です)
- 生き残ったミュータントをキルできるようにテストを強化します
ミューテーションの指標:
- ミューテーションスコア = キル数 /(キル数 + 生存数)
- 目標:ミューテーションスコア > 80%
- 生き残ったミュータント = テストが弱い
成功に不可欠な要素:
- カバレッジが高い ≠ テストが優れている(カバレッジ 100%、アサーション 0%)
- ミューテーションテストにより、テストが実際にバグを検出することを証明できます
- まず重要なコードパスに集中します </default_to_action>
クイックリファレンスカード
使用するタイミング
- テストスイートの品質を評価するとき
- テストのアサーションに抜けがないか確認するとき
- テストがバグを検出することを証明するとき
- 重要なリリースの前
ミューテーションスコアの解釈
| スコア | 解釈 |
|---|---|
| 90%+ | テスト品質が非常に優れています |
| 80-90% | 良好です。軽微な改善が必要です |
| 60-80% | 対応が必要です |
| < 60% | 大きな抜けがあります |
一般的なミューテーション演算子
| カテゴリ | 元のコード | ミュータント |
|---|---|---|
| 算術 | a + b | a - b |
| 関係 | x >= 18 | x > 18 |
| 論理 | a && b | a || b |
| 条件 | if (x) | if (true) |
| 文 | return x | (削除) |
ミューテーションテストの仕組み
// Original code
function isAdult(age) {
return age >= 18; // ← Mutant: change >= to >
}
// Strong test (catches mutation)
test('18 is adult', () => {
expect(isAdult(18)).toBe(true); // Kills mutant!
});
// Weak test (mutation survives)
test('19 is adult', () => {
expect(isAdult(19)).toBe(true); // Doesn't catch >= vs >
});
// Surviving mutant → Test needs boundary value
Stryker の使用
# Install
npm install --save-dev @stryker-mutator/core @stryker-mutator/jest-runner
# Initialize
npx stryker init
設定:
{
"packageManager": "npm",
"reporters": ["html", "clear-text", "progress"],
"testRunner": "jest",
"coverageAnalysis": "perTest",
"mutate": [
"src/**/*.ts",
"!src/**/*.spec.ts"
],
"thresholds": {
"high": 90,
"low": 70,
"break": 60
}
}
実行:
npx stryker run
出力:
Mutation Score: 87.3%
Killed: 124
Survived: 18
No Coverage: 3
Timeout: 1
生き残ったミュータントの修正
// Surviving mutant: >= changed to >
function calculateDiscount(quantity) {
if (quantity >= 10) { // Mutant survives!
return 0.1;
}
return 0;
}
// Original weak test
test('large order gets discount', () => {
expect(calculateDiscount(15)).toBe(0.1); // Doesn't test boundary
});
// Fixed: Add boundary test
test('exactly 10 gets discount', () => {
expect(calculateDiscount(10)).toBe(0.1); // Kills mutant!
});
test('9 does not get discount', () => {
expect(calculateDiscount(9)).toBe(0); // Tests below boundary
});
エージェント主導のミューテーションテスト
// Analyze mutation score and generate fixes
await Task("Mutation Analysis", {
targetFile: 'src/payment.ts',
generateMissingTests: true,
minScore: 80
}, "qe-test-generator");
// Returns:
// {
// mutationScore: 0.65,
// survivedMutations: [
// { line: 45, operator: '>=', mutant: '>', killedBy: null }
// ],
// generatedTests: [
// 'test for boundary at line 45'
// ]
// }
// Coverage + mutation correlation
await Task("Coverage Quality Analysis", {
coverageData: coverageReport,
mutationData: mutationReport,
identifyWeakCoverage: true
}, "qe-coverage-analyzer");
エージェント連携のヒント
メモリの名前空間
aqe/mutation-testing/
├── mutation-results/* - Stryker reports
├── surviving/* - Surviving mutants
├── generated-tests/* - Tests to kill mutants
└── trends/* - Mutation score over time
フリート連携
const mutationFleet = await FleetManager.coordinate({
strategy: 'mutation-testing',
agents: [
'qe-test-generator', // Generate tests for survivors
'qe-coverage-analyzer', // Coverage correlation
'qe-quality-analyzer' // Quality assessment
],
topology: 'sequential'
});
関連スキル
- tdd-london-chicago - 効果的なテストを先に作成します
- test-design-techniques - 境界値分析
- quality-metrics - テストの有効性を測定します
忘れないでください
コードカバレッジが高い ≠ テストが優れている。 カバレッジが 100% でも、アサーションが弱ければ役に立ちません。ミューテーションテストにより、テストが実際にバグを検出することを証明できます。
まず重要なパスに集中します。 すべてをミューテーションテストする必要はありません。決済、認証、データ整合性のコードを優先します。
エージェントを使う場合: エージェントはミューテーション分析を実行し、生き残ったミュータントを特定して、それらをキルするために不足しているテストケースを生成します。テスト品質を自動的に改善できます。
実行履歴
ミューテーションテストを実行するたびに、このスキルのディレクトリにある run-history.json に結果を追記します:
node -e "
const fs = require('fs');
const h = JSON.parse(fs.readFileSync('.claude/skills/mutation-testing/run-history.json'));
h.runs.push({date: new Date().toISOString().split('T')[0], mutation_score_pct: SCORE, killed: KILLED, survived: SURVIVED});
fs.writeFileSync('.claude/skills/mutation-testing/run-history.json', JSON.stringify(h, null, 2));
"
各実行の前に run-history.json を読み込み、スコアの推移を追跡します。
スキルの組み合わせ
- ミューテーションテストの前 →
/qe-test-generationを実行して、テストが存在することを確認します - ミューテーション結果の後 →
/qe-coverage-analysisを使って、改善が必要な領域に優先順位を付けます - 品質ゲート →
/qe-quality-assessmentに結果を渡し、リリース可否を判断します
注意点
- jest と vitest の両方がインストールされている場合、Stryker では
--testRunner jestを明示的に指定する必要があります - 日付比較で
>=を>にミューテーションしても、検出されないことがよくあります。境界値テストを追加してください - 500 LOC を超えるファイルを対象にするとタイムアウトします。
--mutateを使って特定の関数に対象を絞ってください --concurrencyの既定値は CPU のコア数ですが、コンテナーではメモリ不足で停止します。値を 2 に設定してください
コメント
Markdown 対応