# Как писать юнит-тесты с ИИ: практическое руководство

URL: https://agentvera.dev/ru/blog/write-unit-tests-with-ai/ · Опубликовано: 2026-10-25

Как писать юнит-тесты с ИИ-агентами, которые ловят баги: характеризационные тесты, пробелы в покрытии, мутационное тестирование и разделение тестов и кода.

**Коротко:**

- ИИ-агент пишет тесты быстро, но тест, который лишь повторяет прочитанный код, ничего не доказывает; просите проверять поведение из спецификации, а не из реализации.
- Покрытие показывает непроверенный код, а мутационное тестирование — слабые тесты: набор тестов, при котором выживает перевёрнутое условие, вас не защищает.
- Хорошо работает разделение: один агент пишет тесты, другой пишет код, и реализатору нельзя править тесты, чтобы они прошли.
- В AgentVera владение файлами не пускает реализатора в папку с тестами, Verify сверяет результат с вашими критериями, а чекпоинты откатывают неудачный ход.

Когда вы пишете юнит-тесты с ИИ, получить сами тесты легко. Попросите любого кодинг-агента написать «тесты для этого модуля», и через несколько минут у вас будет сорок зелёных тестов. Трудно получить тесты, которые падают, когда код неправильный. Агент, который прочитал функцию и затем написал проверки того, что она возвращает, по сути написал эту функцию второй раз.

В этом руководстве разберём, как получать полезные тесты от кодинг-агентов вроде 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](/ru/blog/claude-md-agents-md/).

### Интеграционные тесты

Они работают с настоящей базой данных, очередью или файловой системой. Агенты и здесь тянутся к мокам, и интеграционный тест снова превращается в юнит-тест. Говорите прямо: «используй тестовую базу из `docker-compose.test.yml`; не мокай слой репозитория». Попросите и очистку, чтобы тесты не зависели от того, что оставили другие.

### End-to-end тесты

Для браузерных тестов агент может написать спецификации Playwright или Cypress по описанному вами пользовательскому сценарию. Слабое место — селекторы: просите селекторы по роли или подписи (`getByRole('button', { name: 'Pay' })`), а не CSS-пути, которые сломаются при следующем редизайне. О том, как дать агенту управлять настоящим браузером для проверки собственной работы, рассказано в статье [тестирование в браузере с ИИ-агентом](/ru/blog/ai-agent-browser-testing/).

## Характеризационные тесты для кода, который вы пока не понимаете

Иногда спецификации нет. Код старый, автор ушёл, и никто точно не знает, что он делает. Тогда цель меняется: нужны тесты, которые фиксируют, что код делает сегодня, включая баги, чтобы вы заметили, если это изменится.

Это характеризационные тесты, и здесь как раз правильно позволить агенту читать реализацию. Промпт меняется соответственно:

> Напиши характеризационные тесты для `legacy/invoice_export.py`. Фиксируй текущее поведение, включая всё, что похоже на баг; ничего не исправляй. Покрой все ветки, которые найдёшь. Любое поведение, которое выглядит неправильным, отметь комментарием `# SUSPICIOUS:` с объяснением.

Пометки `SUSPICIOUS` станут вашим списком багов на потом. Снапшоты и approval-тесты здесь уместны, если они достаточно маленькие, чтобы их читать. Это первый шаг любого безопасного рефакторинга, о котором подробно рассказано в руководстве [рефакторинг legacy-кода с ИИ](/ru/blog/refactor-legacy-code-with-ai/).

## Покрытие — чтобы найти пробелы, а не чтобы оценивать тесты

Покрытие — хорошая карта и плохая оценка. Используйте его, чтобы показать агенту, куда смотреть:

| Стек | Команда |
| --- | --- |
| 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 перечисляет точные номера непокрытых строк, и их удобно вставлять прямо в промпт.

Хороший цикл:

1. Запустите покрытие и выберите важные модули: биллинг, авторизацию, миграции данных, а не сгенерированный код.
2. Дайте агенту непокрытые строки и спросите, какое поведение за ними стоит.
3. Пусть он напишет тесты на это поведение с ожидаемыми значениями из правил.
4. Перезапустите покрытие и убедитесь, что строки теперь выполняются.

Чего делать не стоит — говорить «доведи покрытие до 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` не меняй.

Это одно из лучших применений агента в тестировании. Задача точная, проверка успеха механическая, а результат — тест, который доказуемо ловит реальный класс багов.

## Один агент пишет тесты, другой — код

Самый надёжный способ не дать агенту подогнать тесты — сделать так, чтобы агент, пишущий код, не мог их трогать.

Схема простая:

1. **Агент-тестировщик.** Получает спецификацию и пишет тесты в `tests/` (или туда, где у вас лежат тесты). Реализацию он не видит и не пишет. Сначала его тесты должны падать, ведь фичи ещё нет.
2. **Агент-реализатор.** Получает ту же спецификацию и падающие тесты и пишет код, пока они не пройдут. Ничего в `tests/` ему править нельзя.
3. **Разногласия возвращаются обратно.** Если реализатор считает тест неверным, он спрашивает агента-тестировщика или вас, а не правит тест.

Это разработка через тестирование, разделённая на двух агентов, и она работает даже между разными вендорами. Некоторые команды предпочитают, чтобы тесты писала одна модель, а реализовывала другая, — так у них не совпадают слепые зоны.

Двум агентам на одной фиче нужна изоляция, чтобы они не мешали друг другу. Обычный ответ — отдельный [git worktree](/ru/glossary/git-worktree/) на агента; см. [git worktree для параллельных агентов](/ru/blog/git-worktree-parallel-agents/). При таком разделении можно оставить обоих и в одном worktree, ведь они пишут в разные папки.

## Ревью тестов, написанных ИИ

Читайте тесты не менее внимательно, чем код. Короткий чек-лист:

- **Ожидаемые значения взяты из правил?** Ищите литералы, а не формулы, скопированные из кода.
- **Упадёт ли тест, если код неправильный?** Возьмите один тест и сломайте код вручную. Если тест остался зелёным, это декорация.
- **Не замокано ли что-то важное?** Моки на границе системы — нормально; моки проверяемого кода — нет.
- **Изменился ли какой-то существующий тест?** В ветке фичи изменённые ожидаемые значения в старых тестах заслуживают вопроса. В диффе это сразу видно через `git diff --stat -- tests/`.
- **Читаются ли имена?** По имени упавшего теста должно быть понятно, что сломалось.
- **Не флаки ли он?** Следите за реальным временем, случайными данными, сетевыми вызовами и порядком тестов. Попросите агента прогнать новые тесты несколько раз.

Здесь помогает и автоматическое ревью кода. Вторая модель, читающая дифф, часто замечает тест, который ничего не проверяет. Как это работает, рассказано в статье [ИИ-ревью кода](/ru/blog/ai-code-review/).

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

- **«Напиши тесты для этого файла» — и больше ничего.** Получите пересказ реализации.
- **Погоня за цифрой покрытия.** Получите выполненные строки, а не проверенное поведение.
- **Реализатору разрешено править тесты.** Самый быстрый путь к зелёному — изменённая проверка.
- **Обновление снапшотов без чтения.** `-u` превращает регрессию в новый ожидаемый результат.
- **Тесты для кода, который вы скоро удалите.** Тестируйте поведение, которое останется.
- **Флаки-тесты в наборе.** Агент, увидев падение флаки-теста, с радостью «исправит» то, что не было сломано. То же относится к [автоисправлению ошибок CI с ИИ](/ru/blog/auto-fix-ci-failures-with-ai/).

## Как это сделать в AgentVera

AgentVera запускает CLI кодинг-агентов бок о бок в каждом проекте, поэтому схема «тестировщик и реализатор» настраивается за пару минут.

- **Два агента, одна фича.** Откройте в одном проекте агента-тестировщика и агента-реализатора — на одном CLI или на разных, например Claude Code для тестов и Codex для кода. Если хотите вынести работу из основной папки, поместите их в отдельный worktree через панель worktree.
- **Владение файлами не пускает реализатора в тесты.** В диалоге агента-тестировщика добавьте `tests/**` в его зоны владения. Пока он работает, другие агенты не могут править эти файлы. Для агентов Claude Code правка отклоняется ещё до выполнения хуком PreToolUse; Codex и другие CLI получают правило в своих инструкциях. Отклонённые попытки видны на таймлайне команды.
- **Обмен сообщениями между агентами для разногласий.** Если реализатор считает тест неверным, он может написать агенту-тестировщику напрямую и дождаться ответа, а не править тест.
- **Verify проверяет результат.** Опишите в сообщении, что значит «готово», например «все тесты в `tests/billing` проходят, ни один файл тестов не изменён». Verify превращает это в критерии, и когда ход агента заканчивается, отдельный верификатор запускает тесты и отмечает каждый критерий с доказательством. Верификатор может читать репозиторий и запускать команды тестов, но не может править файлы.
- **Ревью кода читает тесты.** Автоматическое ревью проверяет изменения каждого хода выбранной вами моделью Claude и перечисляет замечания с уровнем серьёзности и file:line — хороший второй взгляд на слабые проверки.
- **Чекпоинты для неудачных ходов.** Каждый ход сохраняется снимком, так что если агент переписал половину набора тестов не туда, вы откатываете этот ход и пробуете снова. См. [как отменить изменения Claude Code](/ru/blog/undo-claude-code-changes/).

## Подводим итоги

ИИ делает написание тестов дешёвым, а значит, работа смещается от набора тестов к их оценке. Давайте агенту поведение, а не код, не позволяйте реализации диктовать ожидаемые значения, используйте покрытие как карту, а мутационное тестирование как оценку, и не позволяйте агенту, который пишет код, решать, что значит «прошло». Тогда скорость будет настоящей: набор тестов быстро растёт и всё равно падает, когда что-то ломается.

Читайте также:

- [Verify](/ru/features/verify/)
- [Владение файлами](/ru/features/file-ownership/)
- [Обмен сообщениями между агентами](/ru/features/agent-messaging/)
- [Ревью кода](/ru/features/code-review/)
- [Чекпоинты и повтор сессии](/ru/features/checkpoints-and-replay/)
- [Параллельные кодинг-агенты](/ru/blog/parallel-coding-agents/)

Чтобы попробовать AgentVera, начните с [главной страницы](/ru/).

## Частые вопросы

**Может ли ИИ писать хорошие юнит-тесты?** Да, если им управлять. Агенты хорошо справляются с подготовкой окружения, фикстурами, списками граничных случаев и шаблонным кодом. Слабое место — ожидаемые значения, выведенные из проверяемого кода: тогда тест просто повторяет реализацию. Дайте агенту ожидаемое поведение и проверьте тесты мутационным тестированием.

**Что такое тавтологический тест?** Тест, который проходит по построению: он вычисляет ожидаемое значение так же, как код, мокает именно то, что должен проверять, или проверяет только факт вызова функции. Он остаётся зелёным, даже когда логика неверна.

**Стоит ли добиваться 100% покрытия для тестов от ИИ?** Нет. Покрытие говорит, какие строки выполнились, а не что что-то проверялось. Используйте его, чтобы найти непротестированные места, а качество тестов оценивайте мутационным тестированием или внимательным ревью.

**Должен ли один агент писать и тесты, и код?** Для мелких изменений это нормально. Для важной логики помогает разделение: один агент пишет тесты по спецификации, другой реализует, и реализатор не может менять тесты. Это закрывает самый простой обход — править тест, пока он не пройдёт.

**Какие агенты можно использовать для этого в AgentVera?** Любой CLI-агент, который запускает AgentVera, например Claude Code, Codex или Gemini CLI. Для агентов Claude Code владение файлами блокирует правку заранее; Codex и другие CLI получают правила владения в своих инструкциях.

---

**Об AgentVera.** AgentVera — настольное приложение для macOS, Windows и Linux от Solviera Teknoloji. Оно запускает 18 ИИ-агентов для программирования (Claude Code, Codex, Gemini CLI, Copilot, Cursor, Antigravity, opencode, Hermes, Qwen Code, Kimi Code, Kiro, Amp, Droid, Goose, Vibe, Crush, Aider, Cline) рядом, по проектам, в настоящих терминалах с собственными установками CLI и входами пользователя. В бесплатном тарифе количество агентов не ограничено. Скачать: https://agentvera.dev/ru/download/ · Цены: https://agentvera.dev/ru/pricing/
