Tests Before Trust
Which tests to write first for code you did not write, how to make the agent test behavior instead of fitting the answer, and when a smoke suite is enough.
Lesson content is currently available in Russian.
What you will learn
- →Выбирать первые тесты по критичности сценария, а не по покрытию.
- →Генерировать тесты агентом и проверять, что они ловят реальные поломки.
- →Собирать smoke-набор на Vitest и Playwright, запускаемый одной командой.
Зачем тесты тому, кто не писал код
У вайб-кодера с тестами особые отношения: они ему нужнее, чем классическому разработчику. Автор кода держит его модель в голове и часто замечает, что правка «зацепит вон то место». У вас этой модели нет — код писал агент, и каждая следующая генерация может тихо сломать что-то на другом конце проекта. Тест — это ваш детектор регрессий: формализованное утверждение «приложение ведёт себя вот так», которое проверяется автоматически после каждого изменения.
Переосмыслите роль тестов: это не экзамен для кода, а контракт поведения. Тест «пользователь с неверным паролем не входит» — это зафиксированное требование, которое переживёт десять переписываний функции входа агентом. Пока тест зелёный, вам всё равно, как именно агент переписал внутренности.
Тесты меняют и экономику работы с агентом. Без тестов каждый промпт заканчивается ручным прогоном приложения — пять минут кликов, и так после каждого изменения. С тестами цикл сжимается до одной команды: npm test, десять секунд, и вы знаете, что ключевые сценарии живы. Более того, тесты делают самого агента эффективнее: инструменты уровня Claude Code запускают тесты сами и чинят свои ошибки до того, как показать вам результат.
Важная умеренность: цель — не 100% покрытие. Прототипу с тремя пользователями достаточно smoke-набора из 10–20 тестов на критичные сценарии. Тесты, как и рефакторинг, — инвестиция, и вкладывать её нужно в места с максимальной ценой поломки.
Какие тесты писать первыми
Классическая пирамида тестов (описанная у Фаулера и в статье Хама Воке «The Practical Test Pyramid») делит тесты на юнит (одна функция в изоляции), интеграционные (несколько частей вместе) и сквозные end-to-end (приложение целиком через браузер). Классика рекомендует много юнитов и мало e2e — они медленные и хрупкие.
Для vibed-приложения акценты сдвигаются. Кент С. Доддс сформулировал это как «Write tests. Not too many. Mostly integration»: максимум уверенности на вложенный час дают тесты уровня «несколько частей вместе», потому что они проверяют поведение, а не внутренности, — а внутренности агент как раз постоянно переписывает. Юнит-тест на приватную функцию умрёт при первом же рефакторинге; тест «форма логина с неверным паролем показывает ошибку» переживёт любую перестройку кода.
Практический порядок выбора первых тестов — по цене поломки. Составьте список ответов на вопрос «что случится самое страшное, если сломается X». Типовой топ: регистрация и вход (сломано — приложение мертво для всех), главное действие продукта (отметка привычки, создание заказа), всё, что касается денег (оплата, тарифы), всё, что касается прав доступа (пользователь А не видит данные пользователя Б — да, тест на RLS из урока 4 автоматизируется!).
Десять тестов на эти сценарии дают больше защиты, чем сто тестов на вспомогательные функции. Правило первой очереди: каждый пункт вашего «списка ручных проверок» из урока про рефакторинг должен превратиться в автотест — вы и так гоняете эти проверки руками после каждого изменения.
Генерируем тесты агентом — и проверяем сами тесты
Хорошая новость: писать тесты руками не обязательно, агент делает это отлично. Промпт: «напиши Vitest-тесты на текущее поведение функции calcStreak из lib/streaks.ts: обычные случаи, границы (пустой список, один день, разрыв серии), ошибки». Плохая новость: у сгенерированных тестов есть две специфические ловушки.
Ловушка первая — тест, подогнанный под баг. Агент читает код и пишет тест, который проверяет то, что код делает, а не то, что он должен делать. Если calcStreak неправильно считает серию через границу месяца, агент честно закрепит этот баг тестом, и тест будет зелёным. Противоядие: формулируйте ожидания сами, в терминах продукта: «серия 30 января — 2 февраля равна четырём дням; напиши тест на это ожидание, а если код его не проходит — это баг кода, покажи его, но не подгоняй тест».
Ловушка вторая — тест, который ничего не проверяет. Тесты без осмысленных assertions или с замоканным всем подряд всегда зелёные. Проверка одна, и она обязательна: сломайте код нарочно и убедитесь, что тест упал. Это ручная мутационная проверка:
import { expect, test } from 'vitest';
import { calcStreak } from './streaks';
test('серия прерывается пропущенным днём', () => {
const days = ['2026-07-01', '2026-07-02', '2026-07-04'];
expect(calcStreak(days, '2026-07-04')).toBe(1);
});
Теперь временно измените в calcStreak условие сравнения дат — тест обязан покраснеть. Не покраснел — тест бесполезен, переписывайте. Правило: тест считается существующим только после того, как вы видели его красным.
Smoke-набор: Vitest + Playwright одной командой
Соберём минимальный, но честный набор. Два инструмента, оба ставятся агентом за один промпт.
Vitest — для функций и логики: расчёты, валидация, преобразования данных. Быстрый (миллисекунды на тест), запускается командой npx vitest run.
Playwright — для сквозных сценариев: реальный браузер кликает по вашему приложению как пользователь. Тест входа выглядит так:
import { expect, test } from '@playwright/test';
test('вход с верным паролем ведёт на список привычек', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('test@example.com');
await page.getByLabel('Пароль').fill('correct-password');
await page.getByRole('button', { name: 'Войти' }).click();
await expect(page).toHaveURL('/habits');
});
Читается как сценарий ручной проверки — потому что им и является, только автоматическим.
Целевой smoke-набор прототипа: 5–8 Playwright-тестов на критичные сценарии из второй секции (вход, главное действие, изоляция данных двух пользователей) плюс 10–15 Vitest-тестов на функции с логикой. Всё вместе должно пробегать за пару минут и запускаться одной командой npm test.
Финальный шаг дисциплины — сделать прогон обязательным. Локально: тесты гоняются перед каждым коммитом. На GitHub: агент настроит GitHub Actions так, чтобы каждый push запускал набор автоматически. С этого момента правило простое: красные тесты — изменение не едет дальше, и неважно, кто его написал — вы или агент.
Practice
Smoke-набор для прототипа
Составьте топ-5 сценариев по цене поломки, сгенерируйте агентом 3 Playwright-теста и 5+ Vitest-тестов на них, проведите мутационную проверку каждого (сломайте код — тест должен упасть) и подключите прогон к npm test.
Deliverable
Репозиторий с папкой тестов, вывод зелёного прогона npm test и заметка: какие тесты падали при мутационной проверке с первого раза, какие пришлось переписать.
Checkpoint
Агент сгенерировал 20 тестов, все зелёные с первого запуска. Почему это подозрительно и как проверить, что тесты настоящие?
- ✓ Названа ловушка: тесты могли зафиксировать текущее поведение вместе с багами или не проверять ничего.
- ✓ Описана мутационная проверка: нарочно сломать код и убедиться, что тесты краснеют.
- ✓ Упомянуто, что ожидания теста формулируются от продукта, а не от текущего кода.
Check yourself
Sources