После запуска мутационного анализа исходный набор проверок проходит и для оригинальной функции, и для варианта с заменой <= на <. Какой вывод об эффективности тестов следует сделать?
function deliveryFee(weight) {
if (weight <= 5) return 100;
return 200;
}
const cases = [[1, 100], [10, 200]];
const passed = cases.every(([weight, expected]) =>
deliveryFee(weight) === expected
);
console.log(passed);
Выживший мутант показывает, что тестовый набор не способен обнаружить правдоподобное изменение логики. В данном случае проверки не различают условия weight <= 5 и weight < 5, потому что среди входов нет значения ровно 5. Это признак недостаточной чувствительности тестов, а не доказательство корректности изменённой функции.
Мутационное тестирование появилось как способ оценивать не только выполнение кода, но и способность тестов обнаруживать ошибки. Одного покрытия строк или ветвей недостаточно: тест может выполнить условие, но не проверить результат с нужной точностью.
Подход моделирует небольшие реалистичные дефекты — мутации — и проверяет, падают ли существующие тесты. Если тесты обнаруживают изменение, мутант считается убитым; если нет, он выживает.
Исходная функция должна брать плату 100 при весе до 5 включительно. Мутированная версия с условием < 5 ошибочно вернёт 200 для веса 5.
Текущие случаи 1 и 10 проверяют две стороны условия, но не саму точку разделения. Поэтому набор тестов проходит для обеих реализаций и может пропустить дефект, который затронет реального пользователя.
Выживший мутант означает, что нужно усилить тесты или проверить, является ли мутация эквивалентной. Здесь мутация неэквивалентна: результаты функций отличаются на входе 5, поэтому корректное усиление — добавить проверку deliveryFee(5) === 100.
После добавления граничного случая мутант с < должен быть уничтожен: для входа 5 он вернёт 200, а ожидается 100. Так мутационный анализ связывает качество тестов с их способностью обнаруживать конкретные изменения поведения.
Мутационный показатель обычно выражают как отношение убитых мутантов к числу анализируемых неэквивалентных мутантов. Однако универсального порога качества нет: результат зависит от набора операторов мутации, исключённых эквивалентных мутантов и критичности системы.
Мутационный анализ не заменяет функциональные, интеграционные и регрессионные проверки. Он также не гарантирует обнаружение требований, которые вообще не представлены в тестах, и может быть вычислительно дорогим для больших проектов.
В расчёте тарифа тесты проверяли малый вес и вес выше лимита, а мутация оператора сравнения выживала. Рассматривались три варианта: увеличить случайное покрытие, добавить только тест на границу или полностью пересмотреть набор сценариев.
Случайное добавление входов имело низкую вероятность надёжно проверить точку разделения. Полный пересмотр был дороже необходимого, поэтому выбрали явный тест для значения 5, а затем добавили аналогичные проверки для нижней и верхней границы допустимого веса.
После этого мутант был уничтожен, а набор стал устойчивее к наиболее вероятной ошибке в условии. Дополнительно команда настроила анализ так, чтобы отдельно отслеживать выжившие мутанты в критичных расчётах, не требуя максимального показателя для всего проекта.
Нет. Иногда мутант эквивалентен исходной программе: изменение текста или структуры не меняет наблюдаемое поведение ни для одного допустимого входа. Такой мутант не может быть уничтожен функциональным тестом и должен быть обоснованно исключён из расчёта.
Нет. Показатель оценивает силу тестов относительно выбранных мутаций, но не полноту требований и не корректность самих ожиданий. Тест с неверным ожидаемым результатом может уничтожать мутант, оставаясь бесполезным для бизнеса, поэтому мутационный анализ нужно сочетать с ревью требований и проверкой тестовых оракулов.
Непокрытая строка вообще не выполняется выбранными тестами. Выживший мутант обычно выполняется, но тесты не замечают изменённый результат или побочный эффект; следовательно, он выявляет более сильную проблему — недостаточную проверку поведения, а не только отсутствие выполнения кода.