一键导入
mutation-testing
Mutation testing patterns for verifying test effectiveness. Use when analyzing branch code to find weak or missing tests.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Mutation testing patterns for verifying test effectiveness. Use when analyzing branch code to find weak or missing tests.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Apply template-downstream to all registered downstream projects in bulk, one project at a time. Use when you want to push the latest .claude/, .mcp.json, or .pre-commit-config.yaml to every project at once. Triggers on: "일괄 배포", "전체 프로젝트에 내려보내기", "모든 프로젝트 업데이트", "broadcast template", "bulk sync".
The standard business voice for any Korean text a human reads — a formal-yet-friendly business register built on 해요체 but elevated with 합니다/습니다 endings, polite recommendation phrasing, natural greetings/closings, and readable structure. Use whenever producing or rewriting reader-facing text: Telegram replies, reports, READMEs, job outputs, human-facing notes, and especially when rewriting raw TIL/technical notes for sharing with colleagues. Triggers: 비즈니스 톤, business voice, 격식 있는 톤, 동료 공유용 재작성, formal rewrite, 보고서 톤, 발표자료 톤.
한국 특화 기능(교통·날씨·부동산·쇼핑·법률·금융·공공데이터 등) 요청이 들어오면 실행. git pull로 최신화 후 README 레지스트리에서 스킬을 골라 해당 SKILL.md만 읽어 진행한다.
Import Dev Sentinel experiences into localdocs/learn.sentinel.md. Use when the user wants to bring gotchas, failure lessons, or struggle experiences captured by Dev Sentinel into the project's local knowledge base. Triggers: "sentinel 가져와", "sentinel import", "gotcha 정리", "경험 가져와", "sentinel에서 배운 것", "실패 경험 가져와".
Update .claude/, .mcp.json, and .pre-commit-config.yaml from the upstream python-project-template repository into the current project. Only files that exist upstream are updated — project-only files are never deleted. Use when pulling latest tooling, rules, hooks, or skills from the canonical template into this project. Triggers on: "템플릿 최신화", "template sync", "upstream 반영", "template 업데이트".
Planning work in small, known-good increments. Use when starting significant work or breaking down complex tasks.
| name | mutation-testing |
| description | Mutation testing patterns for verifying test effectiveness. Use when analyzing branch code to find weak or missing tests. |
Mutation testing answers the question: "Are my tests actually catching bugs?"
Code coverage tells you what code your tests execute. Mutation testing tells you if your tests would detect changes to that code. A test suite with 100% coverage can still miss 40% of potential bugs.
The Mutation Testing Process:
The Insight: A surviving mutant represents a bug your tests wouldn't catch.
Use mutation testing analysis when:
Integration with TDD:
TDD Workflow Mutation Testing Validation
┌─────────────────┐ ┌─────────────────────────────┐
│ RED: Write test │ │ │
│ GREEN: Pass it │──────────► │ After GREEN: Verify tests │
│ REFACTOR │ │ would kill relevant mutants │
└─────────────────┘ └─────────────────────────────┘
When analyzing code on a branch, follow this systematic process:
# Get files changed on the branch
git diff main...HEAD --name-only | grep -E '\.(ts|js|tsx|jsx)$' | grep -v '\.test\.'
# Get detailed diff for analysis
git diff main...HEAD -- src/
For each changed function/method, mentally apply mutation operators (see Mutation Operators section below).
For each potential mutant, ask:
Categorize findings:
| Category | Description | Action Required |
|---|---|---|
| Killed | Test would fail if mutant applied | None - tests are effective |
| Survived | Test would pass with mutant | Add/strengthen test |
| No Coverage | No test exercises this code | Add behavior test |
| Equivalent | Mutant produces same behavior | None - not a real bug |
| Original | Mutated | Test Should Verify |
|---|---|---|
a + b | a - b | Addition behavior |
a - b | a + b | Subtraction behavior |
a * b | a / b | Multiplication behavior |
a / b | a * b | Division behavior |
a % b | a * b | Modulo behavior |
Example Analysis:
// Production code
const calculateTotal = (price: number, quantity: number): number => {
return price * quantity;
};
// Mutant: price / quantity
// Question: Would tests fail if * became /?
// ❌ WEAK TEST - Would NOT catch mutant
it('calculates total', () => {
expect(calculateTotal(10, 1)).toBe(10); // 10 * 1 = 10, 10 / 1 = 10 (SAME!)
});
// ✅ STRONG TEST - Would catch mutant
it('calculates total', () => {
expect(calculateTotal(10, 3)).toBe(30); // 10 * 3 = 30, 10 / 3 = 3.33 (DIFFERENT!)
});
| Original | Mutated | Test Should Verify |
|---|---|---|
a < b | a <= b | Boundary value at equality |
a < b | a >= b | Both sides of condition |
a <= b | a < b | Boundary value at equality |
a <= b | a > b | Both sides of condition |
a > b | a >= b | Boundary value at equality |
a > b | a <= b | Both sides of condition |
a >= b | a > b | Boundary value at equality |
a >= b | a < b | Both sides of condition |
Example Analysis:
// Production code
const isAdult = (age: number): boolean => {
return age >= 18;
};
// Mutant: age > 18
// Question: Would tests fail if >= became >?
// ❌ WEAK TEST - Would NOT catch boundary mutant
it('returns true for adults', () => {
expect(isAdult(25)).toBe(true); // 25 >= 18 = true, 25 > 18 = true (SAME!)
});
// ✅ STRONG TEST - Would catch boundary mutant
it('returns true for exactly 18', () => {
expect(isAdult(18)).toBe(true); // 18 >= 18 = true, 18 > 18 = false (DIFFERENT!)
});
| Original | Mutated | Test Should Verify |
|---|---|---|
a === b | a !== b | Both equal and not equal cases |
a !== b | a === b | Both equal and not equal cases |
a == b | a != b | Both equal and not equal cases |
a != b | a == b | Both equal and not equal cases |
| Original | Mutated | Test Should Verify |
|---|---|---|
a && b | a || b | Case where one is true, other is false |
a || b | a && b | Case where one is true, other is false |
a ?? b | a && b | Nullish coalescing behavior |
Example Analysis:
// Production code
const canAccess = (isAdmin: boolean, isOwner: boolean): boolean => {
return isAdmin || isOwner;
};
// Mutant: isAdmin && isOwner
// Question: Would tests fail if || became &&?
// ❌ WEAK TEST - Would NOT catch mutant
it('returns true when both conditions met', () => {
expect(canAccess(true, true)).toBe(true); // true || true = true && true (SAME!)
});
// ✅ STRONG TEST - Would catch mutant
it('returns true when only admin', () => {
expect(canAccess(true, false)).toBe(true); // true || false = true, true && false = false (DIFFERENT!)
});
| Original | Mutated | Test Should Verify |
|---|---|---|
true | false | Both true and false outcomes |
false | true | Both true and false outcomes |
!(a) | a | Negation is necessary |
| Original | Mutated | Test Should Verify |
|---|---|---|
{ code } | { } | Side effects of the block |
Example Analysis:
// Production code
const processOrder = (order: Order): void => {
validateOrder(order);
saveOrder(order);
sendConfirmation(order);
};
// Mutant: Empty function body
// Question: Would tests fail if all statements removed?
// ❌ WEAK TEST - Would NOT catch mutant
it('processes order without error', () => {
expect(() => processOrder(order)).not.toThrow(); // Empty function also doesn't throw!
});
// ✅ STRONG TEST - Would catch mutant
it('saves order to database', () => {
processOrder(order);
expect(mockDatabase.save).toHaveBeenCalledWith(order);
});
| Original | Mutated | Test Should Verify |
|---|---|---|
"text" | "" | Non-empty string behavior |
"" | "Stryker was here!" | Empty string behavior |
| Original | Mutated | Test Should Verify |
|---|---|---|
[1, 2, 3] | [] | Non-empty array behavior |
new Array(1, 2) | new Array() | Array contents matter |
| Original | Mutated | Test Should Verify |
|---|---|---|
+a | -a | Sign matters |
-a | +a | Sign matters |
++a | --a | Increment vs decrement |
a++ | a-- | Increment vs decrement |
| Original | Mutated | Test Should Verify |
|---|---|---|
startsWith() | endsWith() | Correct string position |
endsWith() | startsWith() | Correct string position |
toUpperCase() | toLowerCase() | Case transformation |
toLowerCase() | toUpperCase() | Case transformation |
some() | every() | Partial vs full match |
every() | some() | Full vs partial match |
filter() | (removed) | Filtering is necessary |
reverse() | (removed) | Order matters |
sort() | (removed) | Ordering is necessary |
min() | max() | Correct extremum |
max() | min() | Correct extremum |
trim() | trimStart() | Correct trim behavior |
| Original | Mutated | Test Should Verify |
|---|---|---|
foo?.bar | foo.bar | Null/undefined handling |
foo?.[i] | foo[i] | Null/undefined handling |
foo?.() | foo() | Null/undefined handling |
| State | Meaning | Action |
|---|---|---|
| Killed | Test failed when mutant applied | Good - tests are effective |
| Survived | Tests passed with mutant active | Bad - add/strengthen test |
| No Coverage | No test exercises this code | Add behavior test |
| Timeout | Tests timed out (infinite loop) | Counted as detected |
| Equivalent | Mutant produces same behavior | No action - not a real bug |
killed / valid * 100 - The higher, the betterkilled + timeoutsurvived + no coverage| Score | Quality |
|---|---|
| < 60% | Weak test suite - significant gaps |
| 60-80% | Moderate - many improvements possible |
| 80-90% | Good - but still gaps to address |
| > 90% | Strong - but watch for equivalent mutants |
Equivalent mutants produce the same behavior as the original code. They cannot be killed because there is no observable difference.
Pattern 1: Operations with identity elements
// Mutant in conditional where both branches have same effect
if (whatever) {
number += 0; // Can mutate to -= 0, *= 1, /= 1 - all equivalent!
} else {
number += 0;
}
Pattern 2: Boundary conditions that don't affect outcome
// When max equals min, condition doesn't matter
const max = Math.max(a, b);
const min = Math.min(a, b);
if (a >= b) { // Mutating to <= or < has no effect when a === b
result = 10 ** (max - min); // 10 ** 0 = 1 regardless
}
Pattern 3: Dead code paths
// If this path is never reached, mutations don't matter
if (impossibleCondition) {
doSomething(); // Mutating this won't affect behavior
}
When analyzing code changes on a branch:
// Original weak test
it('validates age', () => {
expect(isAdult(25)).toBe(true);
expect(isAdult(10)).toBe(false);
});
// Strengthened with boundary values
it('validates age at boundary', () => {
expect(isAdult(17)).toBe(false); // Just below
expect(isAdult(18)).toBe(true); // Exactly at boundary
expect(isAdult(19)).toBe(true); // Just above
});
// Original weak test - only tests one branch
it('returns access result', () => {
expect(canAccess(true, true)).toBe(true);
});
// Strengthened - tests all meaningful combinations
it('grants access when admin', () => {
expect(canAccess(true, false)).toBe(true);
});
it('grants access when owner', () => {
expect(canAccess(false, true)).toBe(true);
});
it('denies access when neither', () => {
expect(canAccess(false, false)).toBe(false);
});
// Weak - uses identity values
it('calculates', () => {
expect(multiply(10, 1)).toBe(10); // x * 1 = x / 1
expect(add(5, 0)).toBe(5); // x + 0 = x - 0
});
// Strong - uses values that reveal operator differences
it('calculates', () => {
expect(multiply(10, 3)).toBe(30); // 10 * 3 != 10 / 3
expect(add(5, 3)).toBe(8); // 5 + 3 != 5 - 3
});
// Weak - no verification of side effects
it('processes order', () => {
processOrder(order);
// No assertions!
});
// Strong - verifies observable outcomes
it('processes order', () => {
processOrder(order);
expect(orderRepository.save).toHaveBeenCalledWith(order);
expect(emailService.send).toHaveBeenCalledWith(
expect.objectContaining({ to: order.customerEmail })
);
});
For automated mutation testing, use Stryker:
npm init stryker
{
"testRunner": "jest",
"coverageAnalysis": "perTest",
"reporters": ["html", "clear-text", "progress"],
"mutate": ["src/**/*.ts", "!src/**/*.test.ts"]
}
npx stryker run
npx stryker run --incremental
The key question for every line of code:
"If I introduced a bug here, would my tests catch it?"
For each test, verify it would catch:
Remember:
>= vs > (boundary not tested)&& vs || (only tested when both true/false)+ vs - (only tested with 0)* vs / (only tested with 1)some() vs every() (only tested with all matching)| Avoid | Use Instead |
|---|---|
| 0 (for +/-) | Non-zero values |
| 1 (for */) | Values > 1 |
| Empty arrays | Arrays with multiple items |
| Identical values for comparisons | Distinct values |
| All true/false for logical ops | Mixed true/false |