← Vibe Coding — and How to Take It to Production
06 / 07 · 55 min

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

Почему тесты особенно важны именно для кода, сгенерированного агентом?

01Почему тесты особенно важны именно для кода, сгенерированного агентом?

Какие тесты дают вайб-кодеру максимум уверенности на вложенный час?

02Какие тесты дают вайб-кодеру максимум уверенности на вложенный час?

Что такое «тест, подогнанный под баг»?

03Что такое «тест, подогнанный под баг»?

Как убедиться, что сгенерированный тест действительно что-то проверяет?

04Как убедиться, что сгенерированный тест действительно что-то проверяет?

Какой инструмент проверяет приложение через реальный браузер, как пользователь?

05Какой инструмент проверяет приложение через реальный браузер, как пользователь?

Sources

Tests Before Trust · Vibe Coding — and How to Take It to Production · VORCL