Mutation Testing

Mutation Testing — техника проверки качества тестов: инструмент вносит небольшие изменения (мутации) в исходный код, и хорошие тесты должны «поймать» эти мутации (упасть); если тесты проходят — они неэффективны.

Зачем нужно

Coverage 100% не гарантирует качество тестов — можно покрыть строку без единого утверждения. Mutation testing отвечает на вопрос «насколько тесты реально проверяют логику?». Это мета-тестирование: тестируем сами тесты.

Где используется

  • Критичные модули с бизнес-логикой (расчёты, валидация, авторизация)
  • Аудит качества тест-сьюта перед рефакторингом
  • Учебные проекты для понимания принципов TDD

Основной контент

Как работает mutation testing

1. Инструмент берёт исходный файл
2. Вносит мутацию (изменение одного оператора)
3. Запускает все тесты
4. Если тест упал — мутант "убит" (тест работает)
5. Если тест прошёл — мутант "выжил" (тест слабый)

Mutation Score = убитые мутанты / все мутанты × 100%

Типы мутаций

Тип Пример мутации
Arithmetic a + ba - b
Conditional >>=, ===!==
Logical &&||
Return value return truereturn false
Increment i++i--

Stryker Mutator — инструмент для JS/TS

npm install --save-dev @stryker-mutator/core @stryker-mutator/jest-runner
npx stryker init   # создаёт конфиг
npx stryker run
// stryker.config.js
/** @type {import('@stryker-mutator/api/core').StrykerOptions} */
module.exports = {
  testRunner: 'jest',
  reporters: ['html', 'clear-text', 'progress'],
  coverageAnalysis: 'perTest',
  mutate: ['src/**/*.js', '!src/**/*.test.js'],
};

Пример: слабый тест vs сильный

// Исходный код
function isAdult(age) {
  return age >= 18;
}

// Слабый тест — мутация age >= 18 → age > 18 не поймана
test('работает', () => {
  expect(isAdult(20)).toBe(true);
});

// Сильный тест — мутант убит
test('18 лет — взрослый (граничное значение)', () => {
  expect(isAdult(18)).toBe(true);  // ловит >= → >
  expect(isAdult(17)).toBe(false); // ловит >= → <=
});

Интерпретация отчёта

Mutation score: 75%   (хорошо ≥ 80%)

Выжившие мутанты (слабые места):
src/discount.js line 12: >= changed to >
  Исходный код:   if (total >= 1000) discount = 0.1;
  Мутант:         if (total > 1000)  discount = 0.1;
  → Нет теста для значения 1000 (граничный случай)

Частые ошибки

  • Запуск на всём проекте — медленно; начинай с конкретных критичных файлов через mutate в конфиге
  • Mutation score 100% как цель — некоторые мутанты убиваются только тривиальными тестами; целесообразный порог 75–85%
  • Путаница coverage vs mutation score — coverage показывает что код выполнен, mutation score — что логика проверена

Связанные темы

Ресурсы