Что делать, если ИИ пишет тесты-пустышки
ИИ-агент пишет тесты, которые проходят и покрывают 100% строк, но ничего не проверяют — happy-path и ассерты-пустышки. Разбираю на реальных кейсах своего проекта dev-pomogator: где зелёные тесты обманывали и что ловило баг на самом деле.
⚙️ Почему покрытие врёт
Покрытие меряет «строка выполнилась», а не «поведение проверено». Можно прогнать каждую строку и не проверить ни одного результата. С ИИ разрыв нагляднее: в исследовании MutGen у LLM-сгенерированных тестов бывает 100% покрытия при 4% mutation score — каждая строка отрабатывает, а 96% внесённых поломок проходят насквозь.
🎯 Как выглядит пустышка
ИИ пишет их не со зла: оптимизирует под «зелёное» и покрытие, а не под «поймать баг». Три самых частых вида:
▸ Ассерт-пропускалка: toBeDefined() зелёный для null, 0, пустой строки и объекта неправильной формы — проверка есть, а пропускает почти всё.
▸ Тавтология: тест считает ожидаемое той же формулой, что и код — ошибка применится к обеим сторонам, тест останется зелёным.
▸ Игрушечный вход: тест «раннер запускает хук» скармливал console.log("OK") — не хук; зелёный, а настоящие хуки с импортами были сломаны.
Самопроверка на каждый тест: упадёт ли он, если подменить весь прод-код на return null? Если нет — перед тобой пустышка.
→ Но и mutation врёт
Mutation ломает рабочий код по одной поломке и смотрит, упадёт ли тест. Полезно — но не панацея. Мой живой баг: дашборд показывал каждую git-worktree по 5 раз. Код решал «это git-репозиторий?» по наличию .git, но у worktree .git — файл-указатель, а не папка. Список перемножался сам на себя.
У этого кода было 100% покрытия, 97.8% mutation score и 10 зелёных тестов. Баг жил в проде месяцами. Почему mutation не поймал? Фикс — добавить проверку строже (.exists() → .is_dir()), а mutation ломает уже написанный код — недостающей проверки не было, ломать нечего.
Поймал баг инвариант на результат: «1 main + 2 linked worktree → ровно 3 ряда, не 9». Одна строка assert, что путь не повторяется.
✅ Как ловить на самом деле
▸ Не верь покрытию — 100% строк ничего не гарантируют.
▸ Прогони mutation — но помни, что и высокий score врёт.
▸ Добавь output-инварианты: «нет дублей», «сколько на входе — столько на выходе», «результат отсортирован». Ловят баги композиции, которые мутатор структурно не видит.
▸ Сверяйся с реальным артефактом, а не с выдуманной фикстурой.
▸ Гейт по факту (упал ли тест под мутантом), а не по проценту.
Весь тулинг я собрал в открытый скил strong-tests (MIT): гоняет mutation, подсказывает инварианты и property-based тесты, ищет 8 типов пустышек. Код всех кейсов и полный разбор — в полной статье на блоге.
