Дополнительный источник: https://lzw.me/docs/opencodedocs/obra/superpowers/core-workflows/tdd-workflow/index.html
Зачем это нужно
Пятый шаг базового workflow (урок 17.5) — и единственный скилл категории Testing. Общие основания TDD с агентами курс разбирал в уроке 9.4; здесь — перевод скилла test-driven-development, и его особенность не в объяснении TDD, а в принуждении: скилл построен как код, формирующий поведение агента, — с «железным законом», таблицами отмазок и красными флагами. Это самый плотный образец стиля Superpowers: не «пожалуйста, пишите тесты», а «вы не имеете права писать код».
Почему TDD — сквозная дисциплина всего Superpowers: план (урок 17.8) пишется в шагах RED→GREEN→Commit; оба режима исполнения (урок 17.9) требуют наблюдать падение каждого теста; верификация (урок 17.13) не принимает «тесты пройдут». Скилл — общее ядро всех этих шагов.
Основная идея
Весь скилл сжимается в «железный закон»:
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
Нет продакшн-кода без упавшего сначала теста. Написали код до теста — удаляется. Не «отложить в ссылку», не «адаптировать при написании тестов», не «посмотреть на него»: удаление означает удаление. Дальше — три фазы цикла, каждая с обязательной верификацией: RED (написать минимальный тест, увидеть падение) → GREEN (минимальный код, увидеть зеленое) → REFACTOR (почистить, оставаясь зеленым). Ключевой принцип скилла: «If you didn't watch the test fail, you don't know if it tests the right thing» — если вы не видели, как тест падает, вы не знаете, тестирует ли он правильное.
Когда применяется
Всегда: новые фичи, багфиксы, рефакторинг, изменения поведения. Исключения — только с согласия партнера: throwaway-прототипы, генерированный код, конфигурационные файлы. Думаете «пропустить TDD только этот раз» — стоп: это рационализация (таблица ниже).
RED: написать падающий тест
Один минимальный тест, показывающий, что должно происходить. Пример «хорошего» теста из скилла:
test('retries failed operations 3 times', async () => {
let attempts = 0;
const operation = () => {
attempts++;
if (attempts < 3) throw new Error('fail');
return 'success';
};
const result = await retryOperation(operation);
expect(result).toBe('success');
expect(attempts).toBe(3);
});
Ясное имя, тестирует реальное поведение, одну вещь.
Требования к тесту: одно поведение; ясное имя; реальный код (моки — только если неотвратимо).
Верифицировать RED: увидеть падение
Обязательно. Никогда не пропускать.
npm test path/to/test.test.ts
Подтвердить: тест падает (не «ошибается»); сообщение об ошибке — ожидаемое; падает потому, что фичи нет (не из-за опечатки). Противоположные случаи: тест прошел — вы тестируете существующее поведение, правьте тест; тест «ошибся» (не упал ассерт) — исправить ошибку, перезапускать, пока не упадет правильно.
GREEN: минимальный код
Простейший код, проходящий тест:
async function retryOperation<T>(fn: () => Promise<T>): Promise<T> {
for (let i = 0; i < 3; i++) {
try {
return await fn();
} catch (e) {
if (i === 2) throw e;
}
}
throw new Error('unreachable');
}
Не добавлять фич, не рефакторить чужой код, не «улучшать» сверх теста. Контрпример из скилла — реализация с options?: { maxRetries; backoff; onRetry } — помечен просто: «YAGNI».
Верифицировать GREEN
Обязательно. Подтвердить: тест проходит; другие тесты тоже проходят; вывод чист (без ошибок и предупреждений). Тест упал — править код, не тест; упали другие — править сейчас.
Отдельное правило скилла про «другие тесты»: «Other tests» means the project's suite, not just your file — зеленый прогон вашего файла не зеленый suite. Перед заявлением «готово» прогоняется тестовая команда проекта (голый pytest, npm test, cargo test), даже если задача называла один файл. «Формулировка scope в задаче ограничивает результат, не вашу верификацию». Любое падение того прогона — включая не вами вызванное — попадает в отчет по имени; «красный тест, который вы видели проскочившим и не упомянули, — отчет, сфальсифицированный умолчанием».
REFACTOR: чистка
Только после зеленого: убрать дублирование, улучшить имена, извлечь хелперы. Тесты остаются зелеными; новое поведение не добавляется. Повторить цикл — следующий падающий тест для следующей фичи.
Хорошие тесты
| Качество | Хорошо | Плохо |
|---|---|---|
| Минимальность | Одна вещь; «and» в имени — разделить | test('validates email and domain and whitespace') |
| Ясность | Имя описывает поведение | test('test1') |
| Намерение | Демонстрирует желаемый API | Скрывает, что код должен делать |
При написании/изменении тестов скилл отсылает к writing-good-tests.md (правила «честных тестов»): назвать продакшн-изменение, которое заставит тест упасть, — до его написания; ассертить реальное поведение, не поведение моков; тест-код — в тест-утилитах, не в продакшн-классах; понять сайд-эффекты зависимости до мокирования.
Отмазки
Таблица рационализаций скилла — перевод выборки:
| Отмазка | Реальность |
|---|---|
| «Слишком просто, чтобы тестировать» | Простой код ломается. Тест занимает 30 секунд. |
| «Протестирую после» | Тесты, написанные после, проходят сразу — что ничего не доказывает: можно тестировать не то, реализацию вместо поведения, пропустить краевой случай. Вы не видели падения — не доказали, что тест ловит баг. |
| «Тесты после достигают того же (дух, не ритуал)» | Тесты-после отвечают «что это делает?», тесты-до — «что это должно делать?». Написанное после смещено кодом, который вы уже написали: проверяете случаи, которые вспомнили. |
| «Уже протестировал руками» | Ручное — ad-hoc: без записи покрытия, без перезапуска при изменениях. «Работало, когда пробовал» ≠ исчерпывающе. |
| «Удалять X часов — расточительно» | Ошибка невозвратных затрат. Реальный выбор: переписать с TDD (высокая уверенность) или оставить и приклеить тесты (низкая уверенность, вероятные баги). Держать код, которому не доверяете, — и есть расточительство. |
| «Оставлю как референс, тесты напишу сначала» | Вы его адаптируете. Это тестирование-после. Удаление означает удаление. |
| «Нужно исследовать сначала» | Хорошо. Выбросить exploration и начать с TDD. |
| «Тест сложен = дизайн неясен» | Слушайте тест: сложно тестировать = сложно использовать. |
| «TDD замедлит» | TDD и есть прагматичный путь: ловит баги до коммита, предотвращает регрессии, позволяет рефакторить без страха. «Прагматичные» сокращения — отладка в продакшене. |
Красные флаги: стоп и начать заново
- код до теста; тест после реализации; тест прошел сразу;
- не можете объяснить, почему тест упал;
- тесты «добавим позже»; рационализируете «только этот раз»;
- «я уже протестировал руками»; «тесты после достигают того же»;
- «оставлю как референс» / «адаптирую существующий код»;
- «уже потрачено X часов, удалять расточительно»;
- «TDD догматичен, я прагматичен»; «это другое, потому что…».
Все это означает: удалить код и начать заново с TDD.
Пример: багфикс
Сценарий скилла: баг — принимается пустой email.
RED:
test('rejects empty email', async () => {
const result = await submitForm({ email: '' });
expect(result.error).toBe('Email required');
});
Верифицировать RED: npm test → FAIL: expected 'Email required', got undefined — падение по ожидаемой причине.
GREEN:
function submitForm(data: FormData) {
if (!data.email?.trim()) {
return { error: 'Email required' };
}
// ...
}
Верифицировать GREEN: npm test → PASS, suite зеленый. REFACTOR: при необходимости извлечь валидацию для нескольких полей.
Правило интеграции с отладкой (связь с уроком 17.13): найден баг → пишется падающий тест, воспроизводящий его → цикл TDD. Тест доказывает фикс и предотвращает регрессию. Никогда не править баги без теста.
Чек-лист верификации
Перед заявлением «готово»:
- [ ] у каждой новой функции/метода есть тест;
- [ ] каждое падение теста наблюдалось до реализации;
- [ ] каждый тест падал по ожидаемой причине (фичи нет, не опечатка);
- [ ] на каждый тест написан минимальный код;
- [ ] все тесты проходят;
- [ ] вывод чист (без ошибок/предупреждений);
- [ ] тесты используют реальный код (моки — только неотвратимо);
- [ ] краевые случаи и ошибки покрыты.
Не можете отметить все — вы пропустили TDD; начать заново. Финальное правило скилла: Production code → test exists and failed first. Otherwise → not TDD — исключений нет без разрешения партнера.
Когда застряли
| Проблема | Решение |
|---|---|
| Не знаете, как тестировать | Напишите желаемый API; ассерт первым; спросите партнера |
| Тест слишком сложен | Дизайн слишком сложен; упростите интерфейс |
| Приходится мокать все | Код слишком связан; dependency injection |
| Огромный setup | Извлеките хелперы; все еще сложно — упростите дизайн |
Практика
Пройдите один полный цикл руками на маленькой функции (например, slugify для заголовков):
- RED: тест
test('converts Cyrillic title to latin slug')— напишите ассерт до реализации. - Верифицируйте падение; зафиксируйте сообщение об ошибке.
- GREEN: минимальная реализация — без опций и обобщений.
- Прогоните весь suite проекта; убедитесь, что вывод чист.
- REFACTOR при наличии поводов; коммит.
Сверьте себя с чек-листом скилла: все ли восемь пунктов отмечаются?
Краткое резюме
Скилл test-driven-development — железный закон «нет кода без упавшего теста» плюс цикл RED-GREEN-REFACTOR с обязательным наблюдением каждого падения и каждого прохода. Код до теста удаляется — без исключений и «референсов».
- RED — минимальный тест реального поведения; падение наблюдается и проверяется на «ожидаемую причину»;
- GREEN — минимальный код; зеленость проверяется на всем suite проекта, не только на вашем файле;
- REFACTOR — только после зеленого, без нового поведения;
- багфикс — тоже TDD: падающий тест, воспроизводящий баг, до фикса;
- таблица отмазок закрывает «потестирую после», «дух вместо ритуала», «расточительно удалять» — все они ведут к одному: удалить и начать заново.
Вопросы для самопроверки
- Почему «тест, который прошел сразу» — находка, а не удача? Что это говорит о тесте?
- Что означает «минимальный код» в фазе GREEN? Приведите контрпример из скилла.
- Почему scope задачи («правь только этот файл») не ограничивает верификацию? Что означает «отчет, сфальсифицированный умолчанием»?
- Разверните отмазку «тесты после достигают того же»: какие два разных вопроса задают тесты-до и тесты-после?
- Какие исключения из TDD допускает скилл и чье согласие для них требуется?
- Как правило багфикса («никогда без теста») связано с verification-before-completion из урока 17.13?
Что дальше
Код написан по TDD, каждая задача завершена — шестой шаг: урок 17.11 разбирает пару скиллов requesting-code-review / receiving-code-review: как Superpowers диспетчеризует свежий контекст ревьюера и как отвечает на замечания без перформативного согласия.