突变测试 mutation-testing

突变测试是一种软件测试技术,用于验证测试用例的有效性,通过自动引入代码突变来检测弱测试,提高测试覆盖率和软件质量。关键词:突变测试、代码变异、测试评估、软件测试、测试覆盖率。

测试 0 次安装 4 次浏览 更新于 3/7/2026

名称: 突变测试 描述: 使用Stryker(TypeScript/JavaScript)和mutmut(Python)进行突变测试以验证测试有效性。找出即使代码发生突变也能通过的弱测试。用于提高测试质量。 允许工具: Bash, Read, Edit, Write, Grep, Glob, TodoWrite

突变测试

突变测试的专家知识 - 通过引入故意的代码突变来验证您的测试是否真正捕获错误。

核心概念

  • 突变体: 自动引入的小型代码更改
  • 杀死: 测试因突变而失败(好 - 测试捕获了错误)
  • 存活: 测试在突变后通过(坏 - 弱测试)
  • 分数: 杀死的突变体百分比(目标80%+)

TypeScript/JavaScript (Stryker)

安装

# 使用 Bun
bun add -d @stryker-mutator/core @stryker-mutator/vitest-runner

# 使用 npm
npm install -D @stryker-mutator/core @stryker-mutator/vitest-runner

配置

// 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,
}

运行 Stryker

# 运行突变测试
bunx stryker run

# 增量模式(仅更改的文件)
bunx stryker run --incremental

# 特定文件
bunx stryker run --mutate "src/utils/**/*.ts"

# 打开 HTML 报告
open reports/mutation/html/index.html

示例:弱测试

// 源代码
function calculateDiscount(price: number, percentage: number): number {
  return price - (price * percentage / 100)
}

// ❌ 弱: 即使我们突变计算,测试也能通过
test('应用折扣', () => {
  expect(calculateDiscount(100, 10)).toBeDefined() // 太弱了!
})

// ✅ 强: 测试捕获突变
test('正确应用折扣', () => {
  expect(calculateDiscount(100, 10)).toBe(90)
  expect(calculateDiscount(100, 20)).toBe(80)
  expect(calculateDiscount(50, 10)).toBe(45)
})

Python (mutmut)

安装

uv add --dev mutmut

运行 mutmut

# 运行突变测试
uv run mutmut run

# 显示结果
uv run mutmut results

# 显示特定突变体
uv run mutmut show 1

# 生成 HTML 报告
uv run mutmut html
open html/index.html

常见突变类型

// 算术运算符
// 原始: a + b → a - b, a * b, a / b

// 关系运算符
// 原始: a > b → a >= b, a < b, a <= b

// 逻辑运算符
// 原始: a && b → a || b

// 布尔字面量
// 原始: true → false

突变分数目标

分数 质量 行动
90%+ 优秀 保持质量
80-89% 良好 小改进
70-79% 可接受 关注弱区域
< 60% 需要重大改进

改进弱测试

模式:断言不足

// 之前:突变存活
test('计算总和', () => {
  expect(sum([1, 2, 3])).toBeGreaterThan(0) // 弱!
})

// 之后:突变杀死
test('正确计算总和', () => {
  expect(sum([1, 2, 3])).toBe(6)
  expect(sum([0, 0, 0])).toBe(0)
  expect(sum([])).toBe(0)
})

模式:边界条件

// 之后:测试边界
test('验证年龄边界', () => {
  expect(isValidAge(18)).toBe(true)   // 最小有效
  expect(isValidAge(17)).toBe(false)  // 刚好低于
  expect(isValidAge(100)).toBe(true)  // 最大有效
  expect(isValidAge(101)).toBe(false) // 刚好高于
})

最佳实践

  • 从核心业务逻辑模块开始
  • 在突变测试前确保80%+的覆盖率
  • 增量运行(仅更改的文件)
  • 首先关注重要文件
  • 不要期望100%的突变分数(存在等效突变体)

工作流程

# 1. 首先确保良好覆盖率
bun test --coverage
# 目标:80%+ 覆盖率

# 2. 运行突变测试
bunx stryker run

# 3. 检查报告
open reports/mutation/html/index.html

# 4. 修复存活的突变体
# 5. 重新运行增量
bunx stryker run --incremental
# 或:npx stryker run --incremental

另请参阅

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