Agent Dev 101

Модуль 17 · Урок 17.10

Урок 17.10. Test-driven development: RED-GREEN-REFACTOR

На этой странице
  1. Зачем это нужно
  2. Основная идея
  3. Когда применяется
  4. RED: написать падающий тест
  5. Верифицировать RED: увидеть падение
  6. GREEN: минимальный код
  7. Верифицировать GREEN
  8. REFACTOR: чистка
  9. Хорошие тесты
  10. Отмазки
  11. Красные флаги: стоп и начать заново
  12. Пример: багфикс
  13. Чек-лист верификации
  14. Когда застряли
  15. Практика
  16. Краткое резюме
  17. Вопросы для самопроверки
  18. Что дальше
  19. Источники

Дополнительный источник: 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) не принимает «тесты пройдут». Скилл — общее ядро всех этих шагов.

Основная идея

Весь скилл сжимается в «железный закон»:

text
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» — если вы не видели, как тест падает, вы не знаете, тестирует ли он правильное.

flowchart LR A["RED:<br/>минимальный<br/>падающий тест"] --> B{"Падает по<br/>ожидаемой причине?"} B -->|нет| A B -->|да| C["GREEN:<br/>минимальный код"] C --> D{"Тест проходит,<br/>suite зеленый?"} D -->|нет| C D -->|да| E["REFACTOR:<br/>чистка без<br/>нового поведения"] E --> D D -->|да, чисто| F["Commit →<br/>следующий тест"]

Когда применяется

Всегда: новые фичи, багфиксы, рефакторинг, изменения поведения. Исключения — только с согласия партнера: throwaway-прототипы, генерированный код, конфигурационные файлы. Думаете «пропустить TDD только этот раз» — стоп: это рационализация (таблица ниже).

RED: написать падающий тест

Один минимальный тест, показывающий, что должно происходить. Пример «хорошего» теста из скилла:

typescript
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: увидеть падение

Обязательно. Никогда не пропускать.

bash
npm test path/to/test.test.ts

Подтвердить: тест падает (не «ошибается»); сообщение об ошибке — ожидаемое; падает потому, что фичи нет (не из-за опечатки). Противоположные случаи: тест прошел — вы тестируете существующее поведение, правьте тест; тест «ошибся» (не упал ассерт) — исправить ошибку, перезапускать, пока не упадет правильно.

GREEN: минимальный код

Простейший код, проходящий тест:

typescript
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:

typescript
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:

typescript
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 для заголовков):

  1. RED: тест test('converts Cyrillic title to latin slug') — напишите ассерт до реализации.
  2. Верифицируйте падение; зафиксируйте сообщение об ошибке.
  3. GREEN: минимальная реализация — без опций и обобщений.
  4. Прогоните весь suite проекта; убедитесь, что вывод чист.
  5. REFACTOR при наличии поводов; коммит.

Сверьте себя с чек-листом скилла: все ли восемь пунктов отмечаются?

Краткое резюме

Скилл test-driven-development — железный закон «нет кода без упавшего теста» плюс цикл RED-GREEN-REFACTOR с обязательным наблюдением каждого падения и каждого прохода. Код до теста удаляется — без исключений и «референсов».

  • RED — минимальный тест реального поведения; падение наблюдается и проверяется на «ожидаемую причину»;
  • GREEN — минимальный код; зеленость проверяется на всем suite проекта, не только на вашем файле;
  • REFACTOR — только после зеленого, без нового поведения;
  • багфикс — тоже TDD: падающий тест, воспроизводящий баг, до фикса;
  • таблица отмазок закрывает «потестирую после», «дух вместо ритуала», «расточительно удалять» — все они ведут к одному: удалить и начать заново.

Вопросы для самопроверки

  1. Почему «тест, который прошел сразу» — находка, а не удача? Что это говорит о тесте?
  2. Что означает «минимальный код» в фазе GREEN? Приведите контрпример из скилла.
  3. Почему scope задачи («правь только этот файл») не ограничивает верификацию? Что означает «отчет, сфальсифицированный умолчанием»?
  4. Разверните отмазку «тесты после достигают того же»: какие два разных вопроса задают тесты-до и тесты-после?
  5. Какие исключения из TDD допускает скилл и чье согласие для них требуется?
  6. Как правило багфикса («никогда без теста») связано с verification-before-completion из урока 17.13?

Что дальше

Код написан по TDD, каждая задача завершена — шестой шаг: урок 17.11 разбирает пару скиллов requesting-code-review / receiving-code-review: как Superpowers диспетчеризует свежий контекст ревьюера и как отвечает на замечания без перформативного согласия.

Источники

Обновлено 03.10.2026