Когда вы пишете юнит-тесты с ИИ, получить сами тесты легко. Попросите любого кодинг-агента написать «тесты для этого модуля», и через несколько минут у вас будет сорок зелёных тестов. Трудно получить тесты, которые падают, когда код неправильный. Агент, который прочитал функцию и затем написал проверки того, что она возвращает, по сути написал эту функцию второй раз.
В этом руководстве разберём, как получать полезные тесты от кодинг-агентов вроде Claude Code, Codex или Gemini CLI: что просить, как распознать тесты, которые ничего не доказывают, как измерять их покрытием и мутационным тестированием и как разделить работу так, чтобы агент, пишущий код, не мог тихо подогнать под него тесты.
Почему тесты от ИИ получаются плохими
Агенты отлично справляются с механической частью тестирования: фикстуры, фабрики, моки, параметризованные случаи, шаблон нового файла с тестами. Ошибаются они в том, что делает тест ценным, — в ожидаемом значении.
Когда агент пишет тест, реализация обычно уже лежит у него в контексте. Естественный ход — прочитать код, понять, что он возвращает, и проверить ровно это. Если в коде баг, тест теперь закрепляет баг. Он зелёный, строки покрыты, и он останется зелёным, даже если баг так и не исправят.
Типичные формы этой проблемы:
- Зеркальная логика. Тест вычисляет ожидаемое значение той же формулой, что и код:
expect(price(100, 0.2)).toBe(100 - 100 * 0.2). Если формула неверна, ошибаются оба. - Мок проверяемого объекта. То, что тестируется, замокано, и тест проверяет мок.
- Проверка только вызова.
expect(sendEmail).toHaveBeenCalled()без проверки, что и кому отправлено. - Снапшот на всё. Огромный снапшот, который никто не читает и который обновляют через
-uпри каждом падении. - Проверки, которые не могут упасть.
expect(result).toBeDefined()для функции, которая всегда возвращает объект. - Правка теста ради зелёного. Код не проходит тест, и агент «чинит» ожидаемое значение в тесте вместо кода.
Ничего из этого не придумано ИИ. Люди пишут такие тесты тоже. Просто агенты делают это быстрее и большими партиями, поэтому нужен процесс, который такие тесты отлавливает.
Начинайте с поведения, а не с кода
Самое большое улучшение — сказать агенту, что код должен делать, и не давать реализации диктовать ожидаемые значения.
Слабый промпт:
Напиши юнит-тесты для
src/billing/discount.ts.
Сильнее:
Напиши юнит-тесты для
applyDiscountвsrc/billing/discount.ts. Правила: процентная скидка никогда не опускает сумму ниже нуля; скидки не суммируются, побеждает наибольшая; просроченный код игнорируется, это не ошибка; суммы округляются до 2 знаков, половина вверх. Ожидаемые значения выведи вручную из этих правил, не запуская и не читая реализацию. Если правило расходится с текущим кодом, всё равно напиши тест и скажи, какие тесты падают.
Изменились три вещи. Правила дали вы — или тикет, спецификация, документация. Ожидаемые значения — литералы, выведенные из правил. И падающий тест допустим: возможно, это настоящий баг, а именно для этого тесты и нужны.
Ещё несколько полезных привычек:
- Сначала попросите список случаев. «Перечисли случаи, которые собираешься проверить, по одной строке, прежде чем писать код». Пропущенные граничные случаи (пустой ввод, юникод, часовые пояса, граница ровно в нуле) можно добавить до того, как появится код.
- Назовите границы. Агенты хорошо находят граничные случаи, если указать измерение: «входы на каждой границе, чуть ниже и чуть выше неё».
- Скажите, что не мокать. «Не мокай ничего внутри
src/billing; мокай только HTTP-клиент». С избыточных моков начинается множество тавтологических тестов. - Одно поведение на тест. Тест с понятным именем вроде
ignores_expired_codesпроще ревьюить, чем один тест с двенадцатью проверками.
Юнит-, интеграционные и end-to-end тесты
Один и тот же агент может писать все три вида, но инструкции для них разные.
Юнит-тесты
Быстрые, изолированные, их много. Здесь агенты экономят больше всего времени. Укажите на свой стиль тестов («следуй tests/billing/tax.test.ts»), чтобы не получать каждый раз новую библиотеку хелперов. Пропишите команду тестов в CLAUDE.md или AGENTS.md, чтобы агент запускал тесты сам; подробнее — в статье как писать CLAUDE.md и AGENTS.md.
Интеграционные тесты
Они работают с настоящей базой данных, очередью или файловой системой. Агенты и здесь тянутся к мокам, и интеграционный тест снова превращается в юнит-тест. Говорите прямо: «используй тестовую базу из docker-compose.test.yml; не мокай слой репозитория». Попросите и очистку, чтобы тесты не зависели от того, что оставили другие.
End-to-end тесты
Для браузерных тестов агент может написать спецификации Playwright или Cypress по описанному вами пользовательскому сценарию. Слабое место — селекторы: просите селекторы по роли или подписи (getByRole('button', { name: 'Pay' })), а не CSS-пути, которые сломаются при следующем редизайне. О том, как дать агенту управлять настоящим браузером для проверки собственной работы, рассказано в статье тестирование в браузере с ИИ-агентом.
Характеризационные тесты для кода, который вы пока не понимаете
Иногда спецификации нет. Код старый, автор ушёл, и никто точно не знает, что он делает. Тогда цель меняется: нужны тесты, которые фиксируют, что код делает сегодня, включая баги, чтобы вы заметили, если это изменится.
Это характеризационные тесты, и здесь как раз правильно позволить агенту читать реализацию. Промпт меняется соответственно:
Напиши характеризационные тесты для
legacy/invoice_export.py. Фиксируй текущее поведение, включая всё, что похоже на баг; ничего не исправляй. Покрой все ветки, которые найдёшь. Любое поведение, которое выглядит неправильным, отметь комментарием# SUSPICIOUS:с объяснением.
Пометки SUSPICIOUS станут вашим списком багов на потом. Снапшоты и approval-тесты здесь уместны, если они достаточно маленькие, чтобы их читать. Это первый шаг любого безопасного рефакторинга, о котором подробно рассказано в руководстве рефакторинг legacy-кода с ИИ.
Покрытие — чтобы найти пробелы, а не чтобы оценивать тесты
Покрытие — хорошая карта и плохая оценка. Используйте его, чтобы показать агенту, куда смотреть:
| Стек | Команда |
|---|---|
| Vitest | npx vitest run --coverage |
| Jest | npx jest --coverage |
| pytest (с pytest-cov) | pytest --cov=myapp --cov-report=term-missing |
| Go | go test -coverprofile=cover.out ./..., затем go tool cover -func=cover.out |
Для Vitest нужен установленный провайдер покрытия, например @vitest/coverage-v8. Отчёт term-missing в pytest перечисляет точные номера непокрытых строк, и их удобно вставлять прямо в промпт.
Хороший цикл:
- Запустите покрытие и выберите важные модули: биллинг, авторизацию, миграции данных, а не сгенерированный код.
- Дайте агенту непокрытые строки и спросите, какое поведение за ними стоит.
- Пусть он напишет тесты на это поведение с ожидаемыми значениями из правил.
- Перезапустите покрытие и убедитесь, что строки теперь выполняются.
Чего делать не стоит — говорить «доведи покрытие до 90%». Агент, получивший число, достигнет числа, и самый дешёвый способ — тесты, которые выполняют строки, почти ничего не проверяя.
Мутационное тестирование: проверяют ли тесты хоть что-то?
Мутационное тестирование отвечает на вопрос, на который покрытие ответить не может. Инструмент вносит в код мелкие изменения — меняет > на >=, + на - или возвращает пустое значение — и прогоняет тесты на каждом изменении. Если тесты всё равно проходят, мутант «выжил», и вы нашли место, где тесты на самом деле не проверяют поведение.
Распространённые инструменты:
- JavaScript и TypeScript: StrykerJS. Настройка —
npm init stryker@latest, запуск —npx stryker run. - Python: mutmut. Запустите
mutmut run, затемmutmut browse, чтобы пройтись по выжившим мутантам. Свежим версиям нужна поддержка fork, поэтому на Windows он работает под WSL. - Java: PIT, например
mvn test-compile org.pitest:pitest-maven:mutationCoverage. - Rust: cargo-mutants, устанавливается через
cargo install cargo-mutantsи запускается командойcargo mutants.
На большой кодовой базе мутационные прогоны медленные, поэтому ограничьте их модулем, над которым работаете. Затем отдайте выживших агенту:
В
discount.tsвыжили эти мутанты: (список). Для каждого напиши тест, который падает на мутанте и проходит на оригинале.discount.tsне меняй.
Это одно из лучших применений агента в тестировании. Задача точная, проверка успеха механическая, а результат — тест, который доказуемо ловит реальный класс багов.
Один агент пишет тесты, другой — код
Самый надёжный способ не дать агенту подогнать тесты — сделать так, чтобы агент, пишущий код, не мог их трогать.
Схема простая:
- Агент-тестировщик. Получает спецификацию и пишет тесты в
tests/(или туда, где у вас лежат тесты). Реализацию он не видит и не пишет. Сначала его тесты должны падать, ведь фичи ещё нет. - Агент-реализатор. Получает ту же спецификацию и падающие тесты и пишет код, пока они не пройдут. Ничего в
tests/ему править нельзя. - Разногласия возвращаются обратно. Если реализатор считает тест неверным, он спрашивает агента-тестировщика или вас, а не правит тест.
Это разработка через тестирование, разделённая на двух агентов, и она работает даже между разными вендорами. Некоторые команды предпочитают, чтобы тесты писала одна модель, а реализовывала другая, — так у них не совпадают слепые зоны.
Двум агентам на одной фиче нужна изоляция, чтобы они не мешали друг другу. Обычный ответ — отдельный git worktree на агента; см. git worktree для параллельных агентов. При таком разделении можно оставить обоих и в одном worktree, ведь они пишут в разные папки.
Ревью тестов, написанных ИИ
Читайте тесты не менее внимательно, чем код. Короткий чек-лист:
- Ожидаемые значения взяты из правил? Ищите литералы, а не формулы, скопированные из кода.
- Упадёт ли тест, если код неправильный? Возьмите один тест и сломайте код вручную. Если тест остался зелёным, это декорация.
- Не замокано ли что-то важное? Моки на границе системы — нормально; моки проверяемого кода — нет.
- Изменился ли какой-то существующий тест? В ветке фичи изменённые ожидаемые значения в старых тестах заслуживают вопроса. В диффе это сразу видно через
git diff --stat -- tests/. - Читаются ли имена? По имени упавшего теста должно быть понятно, что сломалось.
- Не флаки ли он? Следите за реальным временем, случайными данными, сетевыми вызовами и порядком тестов. Попросите агента прогнать новые тесты несколько раз.
Здесь помогает и автоматическое ревью кода. Вторая модель, читающая дифф, часто замечает тест, который ничего не проверяет. Как это работает, рассказано в статье ИИ-ревью кода.
Частые ошибки
- «Напиши тесты для этого файла» — и больше ничего. Получите пересказ реализации.
- Погоня за цифрой покрытия. Получите выполненные строки, а не проверенное поведение.
- Реализатору разрешено править тесты. Самый быстрый путь к зелёному — изменённая проверка.
- Обновление снапшотов без чтения.
-uпревращает регрессию в новый ожидаемый результат. - Тесты для кода, который вы скоро удалите. Тестируйте поведение, которое останется.
- Флаки-тесты в наборе. Агент, увидев падение флаки-теста, с радостью «исправит» то, что не было сломано. То же относится к автоисправлению ошибок CI с ИИ.
Как это сделать в AgentVera
AgentVera запускает CLI кодинг-агентов бок о бок в каждом проекте, поэтому схема «тестировщик и реализатор» настраивается за пару минут.
- Два агента, одна фича. Откройте в одном проекте агента-тестировщика и агента-реализатора — на одном CLI или на разных, например Claude Code для тестов и Codex для кода. Если хотите вынести работу из основной папки, поместите их в отдельный worktree через панель worktree.
- Владение файлами не пускает реализатора в тесты. В диалоге агента-тестировщика добавьте
tests/**в его зоны владения. Пока он работает, другие агенты не могут править эти файлы. Для агентов Claude Code правка отклоняется ещё до выполнения хуком PreToolUse; Codex и другие CLI получают правило в своих инструкциях. Отклонённые попытки видны на таймлайне команды. - Обмен сообщениями между агентами для разногласий. Если реализатор считает тест неверным, он может написать агенту-тестировщику напрямую и дождаться ответа, а не править тест.
- Verify проверяет результат. Опишите в сообщении, что значит «готово», например «все тесты в
tests/billingпроходят, ни один файл тестов не изменён». Verify превращает это в критерии, и когда ход агента заканчивается, отдельный верификатор запускает тесты и отмечает каждый критерий с доказательством. Верификатор может читать репозиторий и запускать команды тестов, но не может править файлы. - Ревью кода читает тесты. Автоматическое ревью проверяет изменения каждого хода выбранной вами моделью Claude и перечисляет замечания с уровнем серьёзности и file:line — хороший второй взгляд на слабые проверки.
- Чекпоинты для неудачных ходов. Каждый ход сохраняется снимком, так что если агент переписал половину набора тестов не туда, вы откатываете этот ход и пробуете снова. См. как отменить изменения Claude Code.
Подводим итоги
ИИ делает написание тестов дешёвым, а значит, работа смещается от набора тестов к их оценке. Давайте агенту поведение, а не код, не позволяйте реализации диктовать ожидаемые значения, используйте покрытие как карту, а мутационное тестирование как оценку, и не позволяйте агенту, который пишет код, решать, что значит «прошло». Тогда скорость будет настоящей: набор тестов быстро растёт и всё равно падает, когда что-то ломается.
Читайте также:
- Verify
- Владение файлами
- Обмен сообщениями между агентами
- Ревью кода
- Чекпоинты и повтор сессии
- Параллельные кодинг-агенты
Чтобы попробовать AgentVera, начните с главной страницы.