Модуль 2 · Строим · урок 9 из 13
Тесты как страховочная сетка
В прошлых уроках мы много раз говорили «это страхуют тесты». Пора проверить, что страховка держит. Тесты — главный механизм, который позволяет вайбкодить без чтения каждой строки. Но тесты, написанные тем же агентом, легко превращаются в зеркало: они проверяют, что код делает то, что делает, а не то, что нужно.
Что проверять на каком уровне
Для «Переговорок» сетка выглядит так:
| Уровень | Что проверяет | Пример |
|---|---|---|
| Юнит | Чистые функции и правила | R1: слот кратен 15 минутам; границы дня по Москве |
| Интеграционный | API вместе с настоящей базой | S2: пересечение → 409; два параллельных запроса; отмена чужой брони → 403 |
| E2E | Сценарий пользователя в браузере | вход по ссылке из письма, бронь, отмена |
Интеграционные тесты — с настоящим PostgreSQL, а не с моком. Правило R3 живёт в ограничении базы, и мок его просто не увидит. Агент, которому сказали «напиши тесты», по умолчанию часто мокает слой данных — потому что так быстрее. Запишите в AGENTS.md: «Интеграционные тесты ходят в тестовую базу rooms_test, слой данных не мокать».
E2E-тестов немного, только ключевые сценарии. Вход по почте проверяем по-настоящему — ссылку берём из Mailpit через его API:
import { test, expect } from "@playwright/test";
const MAILPIT = process.env.MAILPIT_URL ?? "http://localhost:8025";
test("S1: сотрудник входит по ссылке и бронирует слот", async ({ page, request }) => {
await page.goto("/login");
await page.getByLabel("Рабочая почта").fill("anna@example.test");
await page.getByRole("button", { name: "Прислать ссылку" }).click();
const mail = await (await request.get(`${MAILPIT}/api/v1/message/latest`)).json();
const link = mail.Text.match(/https?:\/\/\S+\/auth\/verify\S+/)[0];
await page.goto(link);
await page.goto("/rooms/1/week?date=2026-10-19");
await page.getByRole("gridcell", { name: "Вторник 14:00" }).click();
await page.getByLabel("Название").fill("Ретро спринта");
await page.getByRole("button", { name: "Забронировать" }).click();
await expect(page.getByRole("button", { name: /Ретро спринта/ })).toBeVisible();
});Сначала красный
Саймон Уиллисон в гайде Agentic Engineering Patterns выделяет для агентов красно-зелёный цикл: сначала тест, который падает, потом код, после которого он проходит. Падение важно: тест, который ни разу не был красным, мог ничего не проверять.
Правило R4: отменить можно свою бронь, офис-менеджер — любую.
1. Напиши интеграционные тесты: своя отмена → 200, чужая → 403,
офис-менеджер отменяет чужую → 200, отмена отменённой → 409.
2. Запусти и покажи, что тесты падают, и почему именно.
3. Остановись и жди моего подтверждения.Пауза между шагами — момент, когда вы читаете тесты. Это те самые несколько десятков строк, которые курс требует читать всегда: если тест проверяет не то, весь зелёный прогон ничего не стоит. Подробнее о TDD с агентом — в уроке курса «Codex в ежедневной разработке».
Ещё один приём из того же гайда — начинать сессию с прогона тестов. Агент сразу узнаёт, как они запускаются и что уже сломано, и не примет чужую поломку за свою.
Как агент обходит тесты
Когда тест не проходит, у агента два пути: починить код или «починить» тест. Второй иногда короче, и модели им пользуются. Признаки:
- изменились ожидаемые значения в существующих тестах;
- появились
.skip,.onlyили закомментированные проверки; - замокан именно тот модуль, который тест должен проверять;
try/catch, который глотает ошибку, чтобы тест «не падал»;- проверка заменена на более слабую:
toBeDefined()вместо конкретного значения.
Правило в AGENTS.md («не править существующие тесты, чтобы они прошли — сообщи о конфликте») помогает, но проверять всё равно надо. Быстрый способ увидеть, трогал ли агент тесты:
git diff --stat main -- '*.test.ts' '*.spec.ts'
git diff main -- '*.test.ts' '*.spec.ts' | grep -E '^\+.*\.(skip|only)\('Если в задаче «добавь отмену брони» изменились старые тесты — это повод разобраться до коммита.
Проверить, что тест ловит ошибку
Зелёный тест доказывает только то, что он проходит. Чтобы убедиться, что он ловит ошибку, сломайте код намеренно:
Проверь силу тестов для apps/api/src/services/bookings.ts.
По очереди внеси 5 поломок: убери проверку автора в отмене,
поменяй '[)' на '[]' в диапазоне, сдвинь границу 21:00 на 22:00,
разреши бронь в прошлом, верни 200 вместо 409.
После каждой запускай тесты и откатывай поломку.
Дай таблицу: поломка → какой тест упал. Код не коммить.Каждая строка таблицы без упавшего теста — дыра в сетке. Для регулярной проверки есть инструменты мутационного тестирования (например, StrykerJS для TypeScript), но и ручной вариант с агентом за десять минут даёт много.
Связь тестов со спецификацией
Попросите таблицу «правило → тесты» по всем R и S из спецификации:
Пройди по docs/SPEC.md и для каждого правила и сценария найди тесты,
которые его проверяют. Выведи таблицу. Правила без тестов — отдельным списком.Пустые строки — ваш бэклог тестов. Таблицу полезно перегенерировать перед каждым релизом.
Ревью как второй слой
Тесты проверяют то, о чём вы подумали. Ревью ловит то, о чём не подумали. Команда /review в Codex запускает отдельного ревьюера, который выдаёт приоритизированные замечания и не меняет рабочее дерево — подробно в уроке «Ревью кода командой /review». Проверить незакоммиченные изменения из терминала можно и без интерактивной сессии:
codex review --uncommittedПолезно ревьюить в новой сессии, а не в той, где писался код: у автора в контексте все его оправдания.
Попробуйте сами
10–15 мин на рабочем месте- Возьмите одно правило своей спецификации и пройдите красно-зелёный цикл с паузой после красного. Прочитайте тесты до реализации.
- Попросите агента внести пять намеренных поломок и составить таблицу «поломка → упавший тест».
- Запустите
git diff --stat main -- '*.test.ts'на последней ветке и проверьте, менял ли агент старые тесты.
Коротко
- Тесты позволяют не читать каждую строку, но сами тесты читать нужно.
- Интеграционные тесты — с настоящей базой: часть правил живёт только в ней.
- Сначала красный тест, пауза на чтение, потом реализация.
- Агент умеет «чинить» тесты вместо кода — проверяйте дифф тестов.
- Намеренные поломки показывают, ловит ли сетка ошибки.
/reviewв отдельной сессии — второй слой после тестов.