1. Провести риск через первый цикл
Теперь задача — провести выбранный риск через первый TDD-цикл и не дать Claude полезть в production-код раньше времени. Широкий запрос «напиши тесты» уводит его к дешёвым happy-path проверкам — это вы уже видели.
Когда нужно увидеть сам этот ритм без шума от HTTP, БД и побочных эффектов, полезно временно взять компактный сценарий: дальше смотрю на небольшой validator, а денежный refund flow оставляю главным ориентиром по риску.
2. TDD — короткий инженерный ритм, а не магия
Про TDD часто говорят так, будто это либо великая истина, либо религиозный спор на кухне между разработчиками. На практике всё намного спокойнее: он нужен, чтобы вы и Claude двигались в безопасном порядке — сначала проверяемое поведение, потом код, украшения напоследок.
flowchart TD
A[Ожидаемое поведение] --> B[Red: падающий тест]
B --> C[Green: минимальный рабочий код]
C --> D[Refactor: маленькая чистка]
D --> E[Повтор цикла]
Для AI-assisted разработки это особенно полезно, потому что Claude по умолчанию любит быть полезным слишком широко: попросили тест — он и код поправил; попросили фикс — «немного почистил» соседний модуль. TDD убирает расплывчатость: у каждого шага свой смысл, через него не перескакивают.
Ниже — удобная таблица, которую стоит держать в голове во время работы:
| Шаг | Что происходит | Чем помогает Claude Code | Что решаете вы |
|---|---|---|---|
| Red | Пишется тест на ожидаемое поведение | Находит существующий стиль тестов, пишет каркас теста, предлагает assertions | Что именно должно считаться правильным поведением и почему тест должен падать |
| Green | Код приложения меняется минимально | Предлагает самый короткий фикс в пределах scope | Достаточен ли фикс, не вышел ли diff за границы задачи |
| Refactor | Небольшая чистка после green | Помогает упростить имена, убрать дублирование, привести тест к стилю проекта | Нужна ли чистка вообще и не превращается ли она в отдельную задачу |
Видно главное: Claude ускоряет почти каждый шаг, но порядок шагов и критерии перехода между ними задаёте вы.
3. Красный этап: тест должен честно упасть
На красном этапе главное, чтобы тест честно различал старое и новое поведение. В маленьком validator'е это особенно хорошо видно, поэтому смотрю не на весь refund flow, а на одно правило.
Самая важная часть всей петли — не зелёный тест, а честный красный. Многие новички интуитивно гонятся за зелёной галочкой, но без правильного красного шага она ничего не доказывает: сообщает лишь, что тест не возражает. А почему — отдельная, иногда очень грустная история.
Возьмём простой сценарий из Commerce OS: в test strategy для формы заказа зафиксировано — пустой email блокирует отправку и показывает понятную ошибку. Вот так может выглядеть слабый тест по расплывчатому запросу:
import { describe, expect, it } from "vitest";
import { validateCheckoutForm } from "./checkout-validator";
describe("validateCheckoutForm", () => {
it("проверяет форму", () => {
const result = validateCheckoutForm({ email: "", city: "Москва" });
expect(result).toBeDefined(); // тест зелёный почти при любом поведении
});
});
Такой тест выглядит прилично, но пользы в нём примерно как от зонтика в серверной. Функция что-то вернула — отлично. Только баг с пустым email он не ловит.
А вот уже тест, который действительно выражает ожидаемое поведение:
import { describe, expect, it } from "vitest";
import { validateCheckoutForm } from "./checkout-validator";
describe("validateCheckoutForm", () => {
it("не отправляет форму без email", () => {
const result = validateCheckoutForm({ email: "", city: "Москва" });
expect(result.errors.email).toBe("Укажите email"); // до фикса тест должен упасть
});
});
Здесь уже появляется смысл: мы проверяем не «функция существует», а на пустом email возникает определённая ошибка. Тест должен падать до фикса — и падать по правильной причине.
Вот это место очень важное: красный должен быть честно красным, а не «технически упавшим». Падение из-за кривого импорта, сломанной фикстуры, опечатки в пути или несуществующего mock-а — технический шум. Вам нужен сигнал на уровне поведения.
Поэтому полезно давать Claude Code такой запрос:
Прочитай TASK_SPEC.md и текущий validator.
Сначала напиши падающий тест на ожидаемое поведение.
Код приложения пока не меняй.
Коротко объясни, почему тест должен упасть на текущей реализации.
Точная формулировка может отличаться, и это нормально: важен не магический набор слов, а два ограничения: сначала только тест и объясни, почему он должен падать. Пока Claude не объясняет причину падения, доверять тесту рано.
Важное правило: пока у вас нет честно падающего теста, Claude не должен менять код приложения.
Иначе вы рискуете получить зелёный результат без доказательства, что тест вообще отличал «до» от «после».
4. Claude снимает рутину, а не думает за вас
Claude особенно полезен не там, где думает за вас, а там, где снимает рутину. Чем точнее вы держите смысловые решения за собой, тем лучше он закрывает утомительную механическую часть тестовой работы.
Полезно смотреть на это так:
| Claude Code ускоряет | За вами остаётся решение |
|---|---|
| Читает существующие тесты и копирует стиль проекта | Какое поведение должно считаться правильным |
| Быстро пишет imports, describe/it и каркас теста | Почему именно этот сценарий важен для текущего diff |
| Подбирает boilerplate для фикстур и mock-ов | Не подменяет ли mock то, что вы как раз хотели проверить |
| Предлагает осмысленные имена тестов | Выражает ли тест наблюдаемое пользователем поведение, а не внутреннюю реализацию |
| Может сгенерировать несколько вариантов assertions | Какая проверка действительно ловит баг, а не создаёт ложное спокойствие |
| Напоминает про граничные случаи | Какие граничные случаи уместны именно под ваш риск, а не «всё подряд» |
То есть Claude особенно хорош в роли очень быстрого технического ассистента. Он мгновенно находит соседние тесты, повторяет соглашения репозитория, не забывает импортировать нужные вещи и экономит вам время на механике. Это очень много. Но это не означает, что он должен сам решать, что для пользователя в этой задаче является правильным поведением.
Практически это означает ещё одну полезную привычку: тестовую работу удобно выносить в отдельный чистый фокус. Если текущая implementation-сессия уже забита гипотезами, логами и спорными направлениями, попросите Claude в свежем контексте посмотреть только на тест. Так вы уменьшаете шанс, что старая неудачная гипотеза прилипнет и к тестам.
Хороший запрос на ревью уже написанного теста может выглядеть так:
Проверь только что сгенерированный тест.
Ответь явно:
1. Он проверяет поведение пользователя или внутреннюю реализацию?
2. Может ли он пройти, если баг всё ещё существует?
3. Почему именно эта проверка важнее соседних?
Файлы не меняй.
Такой read-only шаг очень полезен: он превращает «ну вроде тест нормальный» в инженерный разговор с конкретными вопросами.
5. Зелёный этап: минимальный код вместо подвига
На зелёном шаге нужен не подвиг, а минимальный рабочий фикс. И здесь AI хочется одновременно похлопать по плечу и привязать к стулу: Claude обожает «помочь с запасом» — поправить код, улучшить названия, обновить соседние helper'ы, сделать пару добрых дел, о которых вы не просили. Цель скучнее и ценнее: красный стал зелёным, diff не распух.
Для нашего примера с проверкой email минимальный код может выглядеть так:
export function validateCheckoutForm(data: { email: string; city: string }) {
const errors: Record<string, string> = {};
if (!data.email.trim()) {
errors.email = "Укажите email";
}
return { errors };
}
Это очень маленький кусок логики, и в этом как раз его сила: код не перепридумывает систему валидации, не вводит абстракции, не трогает соседние поля — закрывает поведение, уже выраженное тестом.
Хорошо работает такой ритм общения с Claude Code:
Исправь только поведение, из-за которого падает тест на пустой email.
Не меняй структуру модуля.
Не добавляй новые зависимости.
После изменения коротко покажи diff и скажи, какой тест должен стать зелёным.
Если после этого Claude внезапно начинает переносить validators в отдельную папку, придумывать helper и улучшать архитектуру формы, это не повод восхищаться, а повод остановить итерацию и сузить рамки. В тестировании, как в ремонте квартиры, «мы тут заодно немного переделали» звучит тревожнее, чем кажется.
6. Рефакторинг после зелёного: только при сигнале
Когда тест стал зелёным, возникает естественное желание привести всё в идеальный вид. Желание хорошее, но без дисциплины оно очень быстро превращается в «раз уж мы здесь, давайте почистим ещё пять файлов» — уже не завершение цикла, а новый рабочий день под другим названием.
Поэтому правило простое: после green допускается только маленький рефакторинг, не меняющий доказанное поведение. Назвать переменную лучше, вынести повторяющуюся проверку в helper, привести тест к стилю пакета, убрать дублирование внутри теста. Но как только cleanup начинает жить своей жизнью — остановитесь: не умещается в пару причин, значит, достоин отдельной задачи. В AI-режиме это особенно важно: Claude делает код «чище на вид», одновременно усложняя историю изменения для review.
Практически полезно держать в голове такой вопрос: если убрать этот cleanup из diff, основная задача останется выполненной? Если да — выносите отдельно, не смешивайте с bugfix. Именно поэтому чтение diff после зелёного шага не менее важно, чем запуск теста: зелёная лампочка говорит, что поведение проходит, а diff — насколько понятно и безопасно вы к этому пришли.
7. Проверка AI-сгенерированного теста на ложь
Самый опасный момент в AI-assisted тестировании — не красный тест, а уверенно зелёный, который ничего не защищает. Красный хотя бы честно сообщает: «у нас проблема». По-настоящему опасен зелёный, который создаёт комфортное, почти домашнее чувство прогресса — этим и вреден.
Полезно держать перед глазами главный вопрос лекции: Could this test pass while the bug still exists?
Или по-русски: мог бы этот тест пройти, если баг всё ещё жив?
Если ответ «да» — тест слабый, даже если он красивый, быстрый и без опечаток.
Вот несколько типичных ловушек:
| Слабая проверка | Почему опасно | Что лучше сделать |
|---|---|---|
|
Функция может вернуть что угодно, баг останется | Проверять конкретную ошибку, значение или блокировку действия |
|
Это проверка внутренней реализации, а не результата для пользователя | Проверять, что форма не отправилась и пользователь увидел сообщение об ошибке |
| Snapshot на весь объект | Может ломаться из-за шума и при этом не ловить бизнес-баг | Ассертить 1–2 ключевых свойства поведения |
| Слишком агрессивный mock | Вы замокали именно то, что хотели проверить | Оставить реальную зависимость или сузить mock до периферии |
| Тест зелёный до фикса | Он не различает старое и новое поведение | Сделать сценарий, который падает именно на текущей проблеме |
На практике это означает, что AI-сгенерированный тест нужно ревьюить так же внимательно, как AI-сгенерированный production-код, а иногда внимательнее: плохой тест не просто бесполезен — он легитимизирует плохой код. Подписывает бумагу «всё в порядке», хотя в комнате пахнет дымом.
Очень помогает отдельный read-only запрос на качество теста:
Оцени только качество теста, а не код приложения.
Скажи:
- проверяет ли он наблюдаемое поведение;
- не привязан ли он к внутренней реализации;
- может ли он пройти при сохранении бага;
- не слишком ли много здесь mock-логики.
Файлы не меняй.
И ещё одна практичная мелочь: если тест корректен, но имя сценария не объясняет, что проверяется, — тоже тревожный сигнал. Название должен работать корректно почти всегда означает, что автор сам не понял, что доказал. У хорошего теста ясность начинается уже с имени.
8. Правила в проекте: фиксируем раз и навсегда
Если вы уже несколько раз поймали себя на одном и том же разговоре с Claude — «сначала падающий тест», «не трогай код до теста», «после green не делай широкий cleanup» — значит, правило пора вынести из головы в проектную память, иначе будете каждый раз заново проводить тот же мини-урок инструменту.
Для этого очень хорошо подходит CLAUDE.md внутри Workflow Kit или самого проекта. Базовый кусок с тестовыми правилами может быть таким:
## Правила тестирования
- При багфиксе сначала пишем падающий regression test.
- Пока тест не написан, код приложения не меняем.
- После green допускается только маленький refactor в пределах текущего diff.
- В отчёте Claude всегда перечисляет:
- какие тесты добавлены,
- почему они падали,
- что именно стало зелёным.
Такой фрагмент не делает Claude волшебно безошибочным, но резко снижает шанс, что новая сессия поедет в «сейчас всё быстренько поправлю и заодно улучшу архитектуру». А ещё это хороший сигнал команде: тестирование здесь — часть инженерного контракта, а не приложение.
И в этом, пожалуй, самая практичная сторона сегодняшней темы. Когда есть test strategy, красный шаг честный, зелёный маленький, а правила TDD зафиксированы, Claude перестаёт быть автоматом с сюрпризами и становится очень быстрым помощником внутри понятного ритма. Для инженера это ценнее любой эффектной магии.
9. TDD как каноничный кейс для режима «по цели»
Всё, что мы разбирали до этого, — пошаговый TDD-цикл: вручную пишете падающий test, просите Claude реализовать минимальный код, читаете diff, запускаете тест, идёте дальше. Это режим по умолчанию, и для большинства задач он — именно то, что нужно. Сейчас мы аккуратно покажем второй режим: он мелькал в уровне 6 как ментальная модель «работа по цели», здесь применяется впервые буквально.
Идея проста. Падающий test — это и есть машинно-проверяемое условие финиша: «этот test проходит, все остальные не падают, lint зелёный». Раз так, Claude может крутить «красный → зелёный → рефакторинг» несколько ходов подряд без вашего подтверждения, пока не достигнет условия или не упрётся в блокер. Реализация (интерактивный запрос, slash-команда вроде /goal, скриптованный цикл через claude -p) зависит от версии; важна именно ментальная модель: опишите финиш так, чтобы машина сама проверила его достижение.
Почему TDD — самый чистый случай для работы «по цели»:
- условие финиша детерминированное — test либо проходит, либо нет (./gradlew test --tests "OrderRefundTest" возвращает чёткий exit code);
- diff ограничен заранее: изменения в производственном коде и в тестах идут в известные файлы, расползание задачи ловится автоматически;
- регрессии ловятся существующим набором тестов на каждом ходе, если цикл запускает их как часть проверки;
- неудача — это явный отказ, а не «Claude сказал, что всё работает». Для кода, который сгенерировал AI, это критично.
Где режим «по цели» ломается и где нужен пошаговый:
Риск мутации теста. Если в разрешённые файлы попадают и производственный код, и сам test-файл, Claude может «починить» test, изменив проверки, вместо того чтобы пройти его реализацией. Защита: запретить запись в test-файл после того, как написан падающий test (некоторые сценарии поддерживают --no-edit-tests или эквивалент; доступность зависит от версии). Альтернатива — раздельные ходы для написания теста и для починки кода, что фактически возвращает вас к пошаговому режиму.
Субъективные критерии приёмки. «UX лучше», «текст звучит естественнее», «архитектура чище» — это не машинно-проверяемое; режим «по цели» превращается в неуправляемое переписывание. Для таких критериев нужен ручной цикл с человеком.
Дизайн теста против генерации теста. Режим «по цели» подходит для реализации поведения к уже существующему правильному test. Если test сам неверный — проверяет деталь реализации, а не поведение (см. раздел 7 про ложь AI-теста), — режим «по цели» закрепит ошибку быстрее, чем ручной цикл. Дизайн тестов машине не делегируется.
Чувствительная зона (уровень 23, тема 1 — высокий риск). Аутентификация, платежи, миграции — даже с tests требуют явного одобрения человека на каждый diff; режим «по цели» здесь неуместен, цена ошибки слишком велика.
Хороший запрос для работы «по цели» в TDD на Commerce OS:
Goal: test в tests/orders/refund_test.ts проходит,
все остальные tests продолжают проходить,
lint и type-check зелёные.
Constraints:
- меняй только src/orders/refund.ts и связанные файлы внутри src/orders/;
- не модифицируй ни один файл с *test* в имени;
- остановись и спроси, если цель недостижима после 2 попыток;
- остановись и спроси, если нужно менять файлы вне src/orders/;
- после каждой попытки запускай целевой test и полный lint.
Работа «по цели» в TDD — это первая и самая безопасная точка для знакомства с этим режимом в курсе. Все остальные применения (управляемая реализация в уровне 18, пилотная миграция в уровне 29, рефакторинг в уровне 27) — тот же режим в более широких границах с дополнительными ограничениями. Освоив его на TDD, вы переносите базовую модель на другие задачи в рамках границ из уровня 6.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ