mutation-testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Mutation Testing

Mutation Testing

Expert knowledge for mutation testing - validating that your tests actually catch bugs by introducing deliberate code mutations.
关于Mutation Testing的专业知识——通过引入刻意的代码突变来验证你的测试是否真的能发现bug。

Core Concept

核心概念

  • Mutants: Small code changes introduced automatically
  • Killed: Test fails with mutation (good - test caught the bug)
  • Survived: Test passes with mutation (bad - weak test)
  • Score: Percentage of mutants killed (aim for 80%+)
  • Mutants:自动引入的微小代码变更
  • Killed:测试在突变后失败(良好——测试发现了bug)
  • Survived:测试在突变后仍通过(不佳——测试薄弱)
  • Score:被杀死的突变体占比(目标80%以上)

TypeScript/JavaScript (Stryker)

TypeScript/JavaScript(Stryker)

Installation

安装

bash
undefined
bash
undefined

Using Bun

Using Bun

bun add -d @stryker-mutator/core @stryker-mutator/vitest-runner
bun add -d @stryker-mutator/core @stryker-mutator/vitest-runner

Using npm

Using npm

npm install -D @stryker-mutator/core @stryker-mutator/vitest-runner
undefined
npm install -D @stryker-mutator/core @stryker-mutator/vitest-runner
undefined

Configuration

配置

typescript
// stryker.config.mjs
export default {
  packageManager: 'bun',
  reporters: ['html', 'clear-text', 'progress'],
  testRunner: 'vitest',
  coverageAnalysis: 'perTest',
  mutate: ['src/**/*.ts', '!src/**/*.test.ts'],
  thresholds: { high: 80, low: 60, break: 60 },
  incremental: true,
}
typescript
// stryker.config.mjs
export default {
  packageManager: 'bun',
  reporters: ['html', 'clear-text', 'progress'],
  testRunner: 'vitest',
  coverageAnalysis: 'perTest',
  mutate: ['src/**/*.ts', '!src/**/*.test.ts'],
  thresholds: { high: 80, low: 60, break: 60 },
  incremental: true,
}

Running Stryker

运行Stryker

bash
undefined
bash
undefined

Run mutation testing

Run mutation testing

bunx stryker run
bunx stryker run

Incremental mode (only changed files)

Incremental mode (only changed files)

bunx stryker run --incremental
bunx stryker run --incremental

Specific files

Specific files

bunx stryker run --mutate "src/utils/**/*.ts"
bunx stryker run --mutate "src/utils/**/*.ts"

Open HTML report

Open HTML report

open reports/mutation/html/index.html
undefined
open reports/mutation/html/index.html
undefined

Example: Weak Test

示例:薄弱测试

typescript
// Source code
function calculateDiscount(price: number, percentage: number): number {
  return price - (price * percentage / 100)
}

// ❌ WEAK: Test passes even if we mutate calculation
test('applies discount', () => {
  expect(calculateDiscount(100, 10)).toBeDefined() // Too weak!
})

// ✅ STRONG: Test catches mutation
test('applies discount correctly', () => {
  expect(calculateDiscount(100, 10)).toBe(90)
  expect(calculateDiscount(100, 20)).toBe(80)
  expect(calculateDiscount(50, 10)).toBe(45)
})
typescript
// Source code
function calculateDiscount(price: number, percentage: number): number {
  return price - (price * percentage / 100)
}

// ❌ WEAK: Test passes even if we mutate calculation
test('applies discount', () => {
  expect(calculateDiscount(100, 10)).toBeDefined() // Too weak!
})

// ✅ STRONG: Test catches mutation
test('applies discount correctly', () => {
  expect(calculateDiscount(100, 10)).toBe(90)
  expect(calculateDiscount(100, 20)).toBe(80)
  expect(calculateDiscount(50, 10)).toBe(45)
})

Python (mutmut)

Python(mutmut)

Installation

安装

bash
uv add --dev mutmut
bash
uv add --dev mutmut

Running mutmut

运行mutmut

bash
undefined
bash
undefined

Run mutation testing

Run mutation testing

uv run mutmut run
uv run mutmut run

Show results

Show results

uv run mutmut results
uv run mutmut results

Show specific mutant

Show specific mutant

uv run mutmut show 1
uv run mutmut show 1

Generate HTML report

Generate HTML report

uv run mutmut html open html/index.html
undefined
uv run mutmut html open html/index.html
undefined

Common Mutation Types

常见突变类型

typescript
// Arithmetic Operator
// Original: a + b → a - b, a * b, a / b

// Relational Operator
// Original: a > b → a >= b, a < b, a <= b

// Logical Operator
// Original: a && b → a || b

// Boolean Literal
// Original: true → false
typescript
// Arithmetic Operator
// Original: a + b → a - b, a * b, a / b

// Relational Operator
// Original: a > b → a >= b, a < b, a <= b

// Logical Operator
// Original: a && b → a || b

// Boolean Literal
// Original: true → false

Mutation Score Targets

突变分数目标

ScoreQualityAction
90%+ExcellentMaintain quality
80-89%GoodSmall improvements
70-79%AcceptableFocus on weak areas
< 60%PoorMajor improvements needed
分数质量等级行动建议
90%+优秀维持现有质量
80-89%良好小幅优化
70-79%合格聚焦薄弱领域
< 60%较差需要大幅改进

Improving Weak Tests

优化薄弱测试

Pattern: Insufficient Assertions

模式:断言不足

typescript
// Before: Mutation survives
test('calculates sum', () => {
  expect(sum([1, 2, 3])).toBeGreaterThan(0) // Weak!
})

// After: Mutation killed
test('calculates sum correctly', () => {
  expect(sum([1, 2, 3])).toBe(6)
  expect(sum([0, 0, 0])).toBe(0)
  expect(sum([])).toBe(0)
})
typescript
// Before: Mutation survives
test('calculates sum', () => {
  expect(sum([1, 2, 3])).toBeGreaterThan(0) // Weak!
})

// After: Mutation killed
test('calculates sum correctly', () => {
  expect(sum([1, 2, 3])).toBe(6)
  expect(sum([0, 0, 0])).toBe(0)
  expect(sum([])).toBe(0)
})

Pattern: Boundary Conditions

模式:边界条件

typescript
// After: Tests boundaries
test('validates age boundaries', () => {
  expect(isValidAge(18)).toBe(true)   // Min valid
  expect(isValidAge(17)).toBe(false)  // Just below
  expect(isValidAge(100)).toBe(true)  // Max valid
  expect(isValidAge(101)).toBe(false) // Just above
})
typescript
// After: Tests boundaries
test('validates age boundaries', () => {
  expect(isValidAge(18)).toBe(true)   // Min valid
  expect(isValidAge(17)).toBe(false)  // Just below
  expect(isValidAge(100)).toBe(true)  // Max valid
  expect(isValidAge(101)).toBe(false) // Just above
})

Best Practices

最佳实践

  • Start with core business logic modules
  • Ensure 80%+ coverage before mutation testing
  • Run incrementally (only changed files)
  • Focus on important files first
  • Don't expect 100% mutation score (equivalent mutants exist)
  • 从核心业务逻辑模块开始
  • 在进行突变测试前确保测试覆盖率达到80%以上
  • 以增量模式运行(仅针对变更的文件)
  • 优先关注重要文件
  • 不要追求100%的突变分数(存在等效突变体)

Workflow

工作流程

bash
undefined
bash
undefined

1. Ensure good coverage first

1. Ensure good coverage first

bun test --coverage
bun test --coverage

Target: 80%+ coverage

Target: 80%+ coverage

2. Run mutation testing

2. Run mutation testing

bunx stryker run
bunx stryker run

3. Check report

3. Check report

open reports/mutation/html/index.html
open reports/mutation/html/index.html

4. Fix survived mutants

4. Fix survived mutants

5. Re-run incrementally

5. Re-run incrementally

bunx stryker run --incremental
bunx stryker run --incremental

or: npx stryker run --incremental

or: npx stryker run --incremental

undefined
undefined

See Also

相关参考

  • vitest-testing
    - Unit testing framework
  • test-quality-analysis
    - Detecting test smells
  • vitest-testing
    - 单元测试框架
  • test-quality-analysis
    - 检测测试异味