Фраза "нужны тесты на тесты" на проектах встречается часто. Звучит как шутка, но проблема за этим стоит серьёзная.
Тесты есть, но насколько хорошо они тестируют приложение? Когда что-то поменяется и сломается, узнаем ли мы об этом? Нет ли ошибок в самих тестах? Чем больше проект, тем насущнее эта проблема.
Test Coverage - сомнительная метрика. Высокий test coverage означает только, что в тестах участвует много кода.
В этом посте я расскажу о mutation testing. В чём суть, возможные минусы и как использовать подход mutation testing уже сейчас.
Идея
Фреймворк делает небольшое изменение в коде и запускает тесты. Тесты упали - значит кейс проверяется. Тесты прошли - значит тестов не хватает.
Пример: тестируется метод
return m > 10;Написали для него тесты:
▫️
m = 0, ожидаем false▫️
m = 100, ожидаем trueФреймворк поменяет код на
m ≥ 10, и ни один тест не упадёт. Значит, текущих тестов недостаточно, нужно проверить условие m = 10
ПрактикаДля JVM языков по факту есть только один фреймворк Pitest с двумя опциями - Gregor and Descartes.
Как работает:
1️⃣ Запускает тесты и анализирует test coverage. Если код не покрыт тестами, то нет смысла мутировать его.
2️⃣ Составляет список мутаций. Здесь два варианта:
🔸 Gregor тестирует по максимуму, в том числе геттеры и сеттеры
🔸 Descartes использует меньше вариаций, но работает быстрее
3️⃣ Для каждого пункта фреймворк меняет байт-код и запускает тесты. Все изменения, для которых тесты прошли, попадают в итоговый репорт
Минусы
❌ Проверяются только несложные изменения бизнес-логики
❌ Долго выполняется: от 10 минут до 6 часов. Для популярных библиотек результаты такие
❌ Сложно читать результаты и понять, каких тестов не хватает. Связано это с тем, что фреймворк работает не с исходным кодом, а с байт-кодом
❌ Gregor плохо совместим с Kotlin и проверяет конструкции, недопустимые на уровне языка. Приходится либо терпеть множество предупреждений, либо исключать некоторые проверки и рисковать бизнес-логикой. Либо использовать Descartes
❌ Слабо развивается и выглядит как pet project
❌ Непонятно как встроить в CI. Так как возможны ложноположительные результаты, репорты нужно проверять вручную. Можно оставить как необязательную опцию для самых мнительных, но проверка занимает много времени и вряд ли будет востребована
Итог: идея интересная и перспективная, но слабо развивается на практике.
Хозяйке на заметку
Подход mutation testing подойдёт для проверки тестов сложного или критичного участка кода:
▫️Поменяйте условие
▫️Верните константу в метод
▫️Удалите тело в void методе
▫️Поменяйте методы местами
▫️Уберите инкремент
▫️Верните null
▫️Поменяйте бизнес-логику
И запустите тесты. Если падают - значит код протестирован хорошо. Если нет - допишите тесты. Главное - вернуть потом изменения обратно🙂