ХЕЛЛОУИН СЕЙЛ-10%
До 31 октября-10%
ХЕЛЛОУИН СЕЙЛ-10%
До 31 октября-10%
ХЕЛЛОУИН СЕЙЛ-10%
До 31 октября-10%
ХЕЛЛОУИН СЕЙЛ-10%
До 31 октября-10%
ХЕЛЛОУИН СЕЙЛ-10%
До 31 октября-10%
ХЕЛЛОУИН СЕЙЛ-10%
До 31 октября-10%

Юнит-тесты в вайбкодинге

Иван Балашов из SOFTSWISS показывает, как устроен юнит-тест, зачем в нём моки и как AI-агент делает код тестируемым и поднимает покрытие до 84%.
Иван Балашов
Разбор лекции Ивана Балашова от 16 сентября 2026 года из библиотеки стримов Guide DAO.
Иван Балашов работает Head of Development в SOFTSWISS. В разработке он больше десяти лет, успел поработать в Ozon и DDL. Последние полтора года Иван активно пользуется LLM, а последний год почти не пишет код вручную.
На предыдущей лекции Ивана спросили, как сделать, чтобы агенты не ломали то, что уже сделали. Тогда он посоветовал заставлять Claude Code, Codex и любых других агентов писать тесты, а на этом стриме объяснил, как тестирование устроено и зачем оно нужно. Тестирование Иван делит на две группы. Тесты кода, юнит- и интеграционные, пишут разработчики, а E2E-тесты проверяют приложение целиком. Эта лекция про юнит-тесты, а интеграционные и E2E-тесты Иван разобрал во второй части.
Для лекции Иван открыл три своих проекта: криптоплатёжку и проекты на Rust и JavaScript. На них он показал, как устроены тесты, потом попросил агента написать новые и попробовал их сломать, чтобы убедиться, что тесты правда помогают.

Нам надо всё покрывать тестами, всё, что мы можем, просто потому что агенты могут нас ломать. Они могут это делать крайне часто.

Три шага юнит-теста

Юнит-тест проверяет небольшой кусок кода, обычно одну функцию или метод. По словам Ивана, любой юнит-тест состоит из трёх шагов. Схему называют AAA, по первым буквам слов Arrange, Act и Assert, и шаги можно подписать в тесте комментариями.
Arrange готовит данные, и обычно это самая длинная часть теста. Код, который пишет Claude или который раньше писали руками, сложнее функции, складывающей два числа. У него есть зависимости, например база данных. Поэтому тесту логина в криптоплатёжке нужны тестовый пользователь, запрос и всё, от чего зависит функция.
Act вызывает проверяемый код, и обычно это одна строка. В тесте логина это вызов обработчика запроса, и больше ничего. Assert проверяет, что функция отработала так, как от неё ждут.
В проекте на Rust схема та же. Тест create pending file сначала готовит базу данных и хранилище S3, заводит тестового пользователя, потом вызывает функцию и проверяет результат. Бывают тесты совсем простые: пароль должен быть не короче восьми символов, а тест передаёт шесть. В проекте на JavaScript у тестов простых компонентов нет arrange, только act и assert, а в тестах сервисов снова есть все три шага.
Иван бэкенд-разработчик и на JavaScript не пишет, но и там узнаёт те же паттерны: от языка к языку подходы не меняются. Он советует их выучить. Тесты напишет агент, но без этой базы разобраться в них будет сложно.

Моки вместо базы данных

Юнит-тест, объясняет Иван, отличают моки. Мок подменяет поведение внутри кода: функция обращается не к настоящей базе данных, а к заглушке, и та возвращает ровно то, что нужно для теста. С моками можно изобразить абсолютно любое поведение кода.
Возьмём тот же логин. Функция проверяет, что email и пароль не пустые, достаёт пользователя по email, убеждается, что аккаунт активен и не отключён, сверяет пароль и выдаёт пару access- и refresh-токенов. На первом шаге база не нужна, а на втором без запроса в неё не обойтись.
Ходить в настоящую базу в каждом тесте разработчику хочется не всегда. В криптоплатёжке есть большой метод, который завершает депозит. Он берёт транзакцию и счёт, проверяет, что депозит ещё не завершён, обрабатывает его, подтверждает и обновляет статус транзакции. Юнит-тест этого метода на моках прошёл за 56 миллисекунд. Интеграционный тест того же метода с настоящей базой в Docker шёл 4,4 секунды, почти в 80 раз дольше. А все 295 юнит-тестов платёжного процессора вместе отработали быстрее одного такого теста.
Писать моки руками не нужно, с ними прекрасно справляется агент. Но понимать, что это такое, Иван считает обязательным: моки будут везде.

Тест фиксирует поведение

Чтобы проверить, что тесты правда работают, Иван поменял местами две проверки в логине: сначала пароль, потом статус пользователя. На первый взгляд ничего не должно измениться. Он запустил тесты, и упал тест returns error for disabled account: он проверяет, что отключённый аккаунт получает ошибку.
Тест упал, потому что изменилось поведение. Дальше два варианта. Если порядок поменяли осознанно, это новое поведение, и тест нужно поправить под него. Если нет, тест поймал ошибку. Стоит вернуть проверки на место, и тот же тест снова проходит.
Агент тоже может сломать код незаметно. Иногда, по словам Ивана, он не правит файлы по одному, а пишет скрипт, который меняет сразу много файлов: так ему кажется оптимальнее и дешевле по токенам. Если при этом агент проверил не весь проект, массовая правка задевает то, что меняться не должно было. Тесты это покажут.
Тесты фиксируют поведение приложения. В Jest это видно прямо по описанию теста: функция get directory contents должна получать папку по ID. Все мы люди, говорит Иван, и забываем, как работает код и как его правильно поддерживать.

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

Как встроить тесты в работу агента

Запускать тесты можно через агента, достаточно написать ему «Запусти тесты». Но подписки есть не у всех, поэтому Иван советует прогонять тесты самому и не тратить на это токены. Для этого он просит агентов подготовить Makefile: команда make test запускает все тесты приложения.
В среднем агенты сейчас пишут тесты хорошо, и водить их за ручку не нужно, считает Иван. Но иногда Claude, по его словам, «срывает башенку» и забывает, как писать тесты. В криптоплатёжке он однажды начал выдумывать тесты как попало, а Ивану нужно, чтобы тесты всегда были одинаковыми. Поэтому Иван сделал для тестов своего агента. Подойдёт и скил, вроде того, что писали на лекции о скилах.
В CLAUDE.md этого проекта есть правило всегда запускать тесты перед коммитом, чтобы убедиться, что ничего не сломано. Там же описано, как тесты запускать, какие к ним требования и как их писать.

Агент покрывает модуль тестами

Насколько код защищён, видно по покрытию тестами, по-английски coverage: это доля кода, которая выполнилась во время тестов. Иван запустил тесты с подсчётом покрытия. Функция, которая проверяет access- и refresh-токены пользователя, оказалась покрыта на 73%. Раз большая часть кода под тестами, Иван уверен, что тесты заметят, если Claude начнёт что-то менять.
Потом Иван нашёл модуль, к которому тестов не было совсем: авторизацию на JWT, через которую приложение выдаёт и проверяет токены. Он заранее знал, что этот код ходит в базу данных. Агенту Иван написал короткий промпт: «Добавь тесты на нашу авторизацию», вставил имя файла и добавил: «Перед тем как начать, напиши мне список проверок, которые ты будешь выполнять».
Агент ответил планом. Он собирался проверить, что модуль выдаёт валидный токен, проставляет company ID, когда он есть, и не проставляет, когда его нет, а сам токен подписан так, как нужно. Ещё агент выписал пять функций без тестов и предупредил, что это не чистые юниты. Иван предложил ему моки, агент добавил в список генерацию пары токенов и refresh-токен, и Иван написал «делай».
Пока агент работал, Иван объяснил, почему тестирование кода и тестирование бизнес-логики совсем разные вещи. В коде бывают корнер-кейсы, то есть редкие пограничные случаи. Например, код не смог подписать access-токен. Если всё настроено правильно, пользователь такой ошибки не увидит никогда. Но обработать её разработчик обязан, чтобы наружу случайно не уехали секретные данные, а пользователь не увидел страшную страницу с ошибкой 500 и не сбежал.
То же с исключениями: в TypeScript необработанное исключение может уронить всё приложение, поэтому на них тоже пишут тесты. Если сайт показывает «Упс, что-то пошло не так», значит, разработчик забыл обработать какой-то корнер-кейс.

Как агент делает код тестируемым

Тем временем агент написал тесты. Для одной только проверки access-токена он сделал много разных тест-кейсов. Первый замер показал покрытие около 24%: часть функций, например генерацию токена и refresh-токен, тесты ещё не вызывали. А дальше агент полез в сам модуль: написанный раньше код оказался нетестируемым.
Агент добавил в код часы и генератор ID. Функции модуля полагались на данные, которые либо генерируются случайно, либо зависят от времени: внутри они брали текущее время и создавали новый UUID. А assert не может сравнить результат с ожидаемым, если тот меняется при каждом запуске.
Иван показал это на простом примере: сравнил в тесте time.Now() с time.Now(). Первый запуск прошёл, повезло, второй упал. Второй вызов выполняется чуть позже первого: дата и секунды совпали, а наносекунды нет. Полагаться на такой тест нельзя.
Часы и генератор ID решают эту проблему: функция больше не берёт время и UUID сама, и в тесте вместо них можно подставить заранее известные значения. По словам Ивана, год назад агенты справлялись с этим очень плохо, а сейчас стало сильно лучше: они умеют писать тестируемый код.
Иван подтянул изменения и снова запустил тесты с подсчётом покрытия. Агент в фоне тоже прогнал тесты и проверил, что всё работает и ничего не деградировало. Покрытие выросло с 20 с лишним процентов до 84%, все функции модуля оказались под тестами.
Напоследок Иван изобразил агента, который нечаянно поменял логику: срок жизни токена стал 14 дней вместо семи. Тесты сразу это поймали: один упал, потому что изменилось время, когда истекает токен. Ради таких моментов Иван и советует тратить на тесты токены и лимиты.

Что из этого взять в работу

Читайте тесты по схеме AAA: найдите подготовку данных, вызов и проверку, и станет понятно, что делают тесты, которые написал агент.
Прежде чем агент начнёт писать тесты, попросите у него список проверок, как сделал Иван. По списку видно, что агент собирается проверять, и план можно поправить до того, как он возьмётся за код.
Запустите тесты с подсчётом покрытия и отдайте агенту модули, где оно низкое.
Если тестам мешают текущее время или случайные значения, разрешите агенту поправить сам код, чтобы часы и генератор ID приходили в функции снаружи. После такой правки прогоните все тесты и убедитесь, что ничего не деградировало.
Пропишите в CLAUDE.md правило прогонять тесты перед каждым коммитом и опишите, как их запускать и писать. Попросите агента сделать Makefile с командой make test, чтобы запускать тесты самому и не тратить на это токены.
Трек: Вайбкодинг
Соберёте с Codex или Claude Code сайт-портфолио, сайт с онлайн-записью для заказчика и своё приложение. Проверите, как они работают, и опубликуете по ссылке.
Подробнее
Поделиться
Интересно погрузиться в крипту?
Поможем выбрать направление!