Перейти к содержимому
PD
Вайб-кодинг 7 мин чтения

Хватит писать планы для кодинг-агента. Пишите тесты

Длинные планы расползаются и устаревают на третьем шаге. Падающие приемочные тесты дают агенту спецификацию, по которой он может сам себя проверить. Рассказываю, как я управляю агентами без plan mode.

PD

Pavel Duglas

AI Automation & MVP Architect

Примерно год я работал с кодинг-агентами по одной схеме. Описываю фичу, агент пишет план, я читаю, правлю и утверждаю план. Потом смотрю, как к четвертому шагу агент уходит от плана куда-то в сторону. Markdown я редактировал дольше, чем ревьюил код. План создавал ощущение контроля, но по факту это был театр. Агент соглашался с каждым пунктом, а потом делал то, что ему подсказывал код перед глазами.

Помогла скучная вещь: я перестал писать планы и начал писать падающие тесты. Тест - это план, который агент не может понять неправильно, не может тихо пропустить и не может объявить выполненным без доказательств. Ниже показываю, как это выглядит на реальных клиентских проектах.

Почему план плохо управляет агентом

План - это текст. А у текста, который читает модель, три проблемы.

Он неоднозначный. “Аккуратно обрабатывать rate limit” можно понять пятью способами. Агент выберет один, и обычно тот, где меньше всего кода.

Его нельзя проверить. Нет команды, которая скажет, выполнен ли третий пункт плана. Агент сообщает, что выполнен, и дальше вы либо верите на слово, либо перечитываете diff построчно.

Он устаревает сразу. Как только агент обнаруживает, что существующий код ведет себя не так, как вы думали, план становится неверным. Хороший агент подстроится, но теперь план у вас в голове и код на диске живут отдельно, и документ никто не обновляет.

У падающего теста этих проблем нет. Он точный, он исполняется, и когда реальность меняется, он громко падает, а не превращается тихо в выдумку.

Основная идея: тесты задают спецификацию, агент пишет реализацию

Роли распределены просто:

  • Я пишу приемочные тесты. Они описывают поведение снаружи: входные данные, результат, побочные эффекты, ошибки.
  • Агент пишет реализацию и любые внутренние unit-тесты, какие захочет.
  • Никто не говорит “готово”, пока не проходит одна команда проверки.

Это не классический TDD с циклом red-green на каждую функцию. Мне все равно, как агент устроит внутренности. Мне важно, чтобы фича правильно вела себя на границе. Именно там баги стоят денег.

Что я кладу в приемочные тесты

Возьмем задачу из реальной практики. У клиента Telegram-бот для небольшого бизнеса с записью на услуги. Фича: пользователь может отменить запись командой, но не позже чем за 24 часа до слота, а в админский чат приходит уведомление.

Раньше я бы написал план на семь пунктов. Сейчас пишу вот так:

# tests/acceptance/test_cancel_booking.py

def test_user_can_cancel_more_than_24h_before(bot, db, clock):
    clock.set("2026-03-01 10:00")
    booking = db.create_booking(user_id=42, slot="2026-03-03 10:00")
    reply = bot.send(user_id=42, text=f"/cancel {booking.id}")
    assert "отмен" in reply.text.lower()
    assert db.get_booking(booking.id).status == "cancelled"

def test_cancel_inside_24h_is_rejected(bot, db, clock):
    clock.set("2026-03-02 11:00")
    booking = db.create_booking(user_id=42, slot="2026-03-03 10:00")
    reply = bot.send(user_id=42, text=f"/cancel {booking.id}")
    assert db.get_booking(booking.id).status == "active"
    assert "24" in reply.text

def test_user_cannot_cancel_someone_elses_booking(bot, db, clock):
    booking = db.create_booking(user_id=42, slot="2026-03-10 10:00")
    bot.send(user_id=99, text=f"/cancel {booking.id}")
    assert db.get_booking(booking.id).status == "active"

def test_admin_is_notified_on_cancel(bot, db, clock, admin_chat):
    clock.set("2026-03-01 10:00")
    booking = db.create_booking(user_id=42, slot="2026-03-05 10:00")
    bot.send(user_id=42, text=f"/cancel {booking.id}")
    assert admin_chat.last_message_contains(str(booking.id))

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

  • Точная граница в 24 часа и то, что время берется из подменяемого clock, а не из datetime.now(), разбросанного по всему коду.
  • Проверка владельца записи. Именно ее агент забыл бы с наибольшей вероятностью.
  • Отклоненная отмена не трогает запись.
  • Побочный эффект в админском чате.

Точную формулировку ответа я специально не проверяю. Достаточно, что в сообщении есть “24”: значит, пользователю объяснили причину. Слишком детальные тесты заставляют агента выкручивать код под конкретные строки, а это тоже своего рода дрейф.

Фикстуры важнее самих тестов

Настоящая инвестиция здесь - фикстуры bot, db, clock и admin_chat. Когда они есть, приемочные тесты для любой следующей фичи обходятся дешево. На новом проекте я первую сессию трачу на эту обвязку сам или с агентом под плотным присмотром. Дальше спецификация на фичу пишется за несколько минут.

Если в проекте вообще нет тестовой обвязки, начинать нужно с нее. Не с фичи.

Правила, которые я даю агенту

Одних тестов мало. Агенты отлично умеют проходить тесты не тем способом, который вы имели в виду. Эти правила лежат в файле инструкций репозитория, поэтому действуют в каждой сессии:

  1. Файлы в tests/acceptance/ только для чтения. Если агент считает тест неправильным, он останавливается и объясняет почему. Править тест он не имеет права. Одно это правило убирает самый частый провал: агент “чинит” тест под свой кривой код.
  2. Никаких особых веток под тестовые условия. Никаких проверок на тестовые user_id и флагов окружения, которые обходят логику. На ревью я специально ищу такое через grep.
  3. Готовность определяет одна команда. У меня это make check: линтер, проверка типов, unit и приемочные тесты. Агент обязан ее запустить и вставить хвост вывода, прежде чем заявить, что все готово.
  4. Минимальный diff, при котором все зеленое. Никакого рефакторинга соседнего кода в той же задаче. Рефакторинг - отдельная задача с зеленой проверкой до и после.

Первое правило чаще всего пропускают и потом жалеют. Я закрепляю его еще и механически: pre-commit hook падает, если в коммите из агентской сессии изменились файлы приемочных тестов. Лишняя страховка не помешает: правило в инструкции - это пожелание, а hook - это факт.

Как выглядит цикл на практике

Последовательность, по которой я делаю фичу:

  1. Пишу приемочные тесты. Запускаю. Убеждаюсь, что они падают по правильной причине: нет команды, а не сломана фикстура.
  2. Коммичу падающие тесты в ветку. Это спецификация, и теперь она под версионным контролем.
  3. Даю агенту короткий prompt: фича в двух предложениях, путь к тестам и “добейся, чтобы make check проходил, не трогая приемочные тесты”.
  4. Отпускаю его работать. За каждым шагом я больше не слежу. Возвращаюсь, когда он пишет, что закончил.
  5. Сам запускаю make check. Агенты иногда отчитываются об успехе по устаревшему прогону.
  6. Смотрю diff с одним конкретным вопросом: он решил задачу или решил тесты? Это разные вещи, и за разницу между ними мне как раз платят.

Обратите внимание, какой короткий третий шаг. Prompt ужался, потому что детали теперь несут тесты. Мои промпты сократились с полстраницы до трех строк, а результат стал лучше.

Когда агент возражает

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

  • Моя спецификация действительно была неправильной. Например, в модели записи слоты хранятся в UTC, а тест исходил из местного времени. Отлично, я нашел реальную неоднозначность до релиза. Правлю тест сам и перезапускаю задачу.
  • Существующий код мешает спецификации. Например, отмена записей намертво переплетена с модулем оплат. Это уже разговор об архитектуре, и я лучше проведу его сейчас, чем обнаружу проблему в diff на 600 строк.

С планами такие конфликты оставались невидимыми. Агент просто тихо прогибал план под себя. С тестами только для чтения конфликт обязан всплыть.

Где планы все еще нужны

Планирование я не запретил совсем. Короткий план я пишу, когда:

  • Это миграция или реструктуризация без нового поведения, которое можно протестировать. Спецификация тогда звучит как “все существующие тесты остаются зелеными”, плюс упорядоченный список шагов, чтобы ничего не осталось переехавшим наполовину.
  • Фича затрагивает несколько сервисов, и важен порядок deploy.
  • Я исследую задачу и еще не знаю, каким должно быть поведение. Тогда я делаю прототип вместе с агентом, выбрасываю код и пишу тесты на то, что понял.

Последний пункт важный. Накидать одноразовый прототип в режиме vibe coding - прекрасный способ разобраться в задаче. Ошибка в том, чтобы этот прототип оставить. Когда стало понятно, чего вы хотите, зафиксируйте это в тестах и соберите заново.

Что это меняет для фаундеров и небольших команд

Если у вас MVP, над которым работают один-два разработчика и агенты, такой процесс меняет то, куда уходит внимание человека. Вы перестаете быть тем, кто читает каждую строчку, написанную агентом. Вы становитесь тем, кто точно определяет, что значит “правильно”. А это как раз та часть, которую модель за вас сделать не может: она не знает ваш бизнес.

Что я вижу на клиентских проектах. Меньше регрессий, когда агенты лезут в старый код, потому что набор приемочных тестов растет и защищает каждую фичу, которую когда-либо специфицировали. Ревью идет быстрее, потому что я проверяю намерение, а не синтаксис. И новому разработчику проще войти в проект: приемочные тесты читаются как список того, что продукт обещает пользователям.

Начните с малого. Возьмите следующую фичу из бэклога, до открытия агента напишите три-пять приемочных тестов, сделайте их read-only и дайте агенту prompt на три строки. Сравните результат с последней фичей, которую делали по плану. Удивлюсь, если захотите вернуться.

Вопросы и ответы

Что делать, если в проекте вообще нет тестов и тестовой обвязки?

Начать с обвязки, а не с фичи. Выделите одну сессию на фикстуры для ключевых точек входа: бот или API, база, подменяемое время, внешние уведомления. Можно делать это вместе с агентом, но под плотным контролем и с ревью каждого файла. После этого приемочный тест на новую фичу пишется за 10-15 минут, и вложение окупается уже на второй-третьей задаче.

Не начнет ли агент просто подгонять код под тесты?

Попытается, если ему не мешать. Поэтому нужны три вещи: приемочные тесты только для чтения с pre-commit hook, запрет на особые ветки под тестовые условия и ревью diff с вопросом "решена задача или решены тесты". Плюс не пишите слишком узкие проверки на точные строки: чем больше тест проверяет поведение, а не формулировки, тем меньше у агента соблазна хитрить.

Подходит ли этот подход для парсеров и браузерной автоматизации, например в BAS?

Для парсеров подходит отлично: сохраните несколько реальных HTML-страниц как фикстуры и опишите в тестах, какие поля и в каком виде должны получиться, включая случаи с пустыми и сломанными блоками. Для браузерных сценариев логику лучше выносить в отдельные функции разбора и принятия решений и тестировать их на сохраненных данных, а сам прогон в браузере оставить для smoke-проверки.

Похожие статьи