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 | Пишеться тест на очікувану поведінку | Знаходить наявний стиль тестів, пише каркас тесту, пропонує перевірки | Що саме має вважатися правильним поводженням і чому тест має падати |
| 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 те, що ви якраз хотіли перевірити |
| Пропонує змістовні імена тестів | Чи виражає тест спостережувану користувачем поведінку, а не внутрішню реалізацію |
| Може згенерувати кілька варіантів перевірок | Яка перевірка справді ловить баг, а не створює хибний спокій |
| Нагадує про граничні випадки | Які граничні випадки доречні саме під ваш ризик, а не «все підряд» |
Тобто 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-цикл: вручну пишете тест, що падає, просите Claude реалізувати мінімальний код, читаєте diff, запускаєте тест, рухаєтеся далі. Це режим за замовчуванням, і для більшості завдань він — саме те, що потрібно. Тепер ми акуратно покажемо другий режим: він миготів на рівні 6 як ментальна модель «робота за метою», а тут застосовується вперше буквально.
Ідея проста. Тест, що падає, — це і є машинно-перевірювана умова фінішу: «цей test проходить, усі інші не падають, lint зелений». Отже, Claude може крутити «червоний → зелений → рефакторинг» кілька ходів поспіль без вашого підтвердження, поки не досягне умови або не впереться в блокер. Реалізація (інтерактивний запит, slash-команда на кшталт /goal, скриптований цикл через claude -p) залежить від версії; важлива саме ментальна модель: опишіть фініш так, щоб машина сама перевірила його досягнення.
Чому TDD — найчистіший випадок для роботи «за метою»:
- умова фінішу детермінована — test або проходить, або ні (./gradlew test --tests "OrderRefundTest" повертає чіткий exit code);
- diff обмежений наперед: зміни у виробничому коді та в тестах ідуть у відомі файли, розповзання завдання ловиться автоматично;
- регресії ловляться наявним набором тестів на кожному ході, якщо цикл запускає їх як частину перевірки;
- невдача — це явна відмова, а не «Claude сказав, що все працює». Для коду, який згенерував AI, це критично.
Де режим «за метою» ламається і де потрібен покроковий:
Ризик мутації тесту. Якщо в дозволені файли потрапляють і виробничий код, і сам test-файл, Claude може «полагодити» 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.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ