Красный-Зелёный-Рефакторинг (TDD цикл)

Red-Green-Refactor — трёхфазный цикл TDD: написать падающий тест (Red), написать минимальный код для прохождения теста (Green), улучшить код без изменения поведения (Refactor).

Зачем нужно

Цикл задаёт ритм разработки: маленькие предсказуемые шаги вместо больших скачков. Red фаза гарантирует что тест проверяет реальное поведение. Green фаза фокусирует на минимальном решении. Refactor фаза обеспечивает качество кода без накопления технического долга.

Где используется

  • Разработка любой бизнес-логики и алгоритмов
  • Фикс багов: сначала тест, воспроизводящий баг — потом фикс
  • Рефакторинг: тесты защищают от регрессий при улучшении кода

Основной контент

Цикл подробно

RED    → Пишешь тест → Запускаешь → Тест ПАДАЕТ (ожидаемо!)
           Падение подтверждает что тест проверяет реальную логику

GREEN  → Пишешь минимальный код → Тест ПРОХОДИТ
           "Минимальный" значит буквально минимальный: можно вернуть константу

REFACTOR → Улучшаешь код → Тесты ВСЁ ЕЩЁ ПРОХОДЯТ
           Убираешь дублирование, улучшаешь имена, применяешь паттерны

Детальный пример: валидатор пароля

RED 1 — первый тест

// passwordValidator.test.js
const { validate } = require('./passwordValidator');

test('пароль короче 8 символов невалиден', () => {
  expect(validate('abc')).toBe(false);
});
// Запускаем → Cannot find module './passwordValidator' — RED ✓

GREEN 1 — минимальная реализация

// passwordValidator.js
function validate(password) {
  return false; // минимум — тест проходит!
}
module.exports = { validate };

RED 2 — следующее требование

test('пароль 8+ символов без спецтребований валиден', () => {
  expect(validate('password')).toBe(true);
});
// RED — возвращаем false для всего

GREEN 2 — обобщаем

function validate(password) {
  return password.length >= 8;
}

RED 3 — требование: нужна заглавная буква

test('пароль без заглавной буквы невалиден', () => {
  expect(validate('password1')).toBe(false);
});

GREEN 3

function validate(password) {
  if (password.length < 8) return false;
  if (!/[A-Z]/.test(password)) return false;
  return true;
}

RED 4 — нужна цифра

test('пароль без цифры невалиден', () => {
  expect(validate('Password')).toBe(false);
});

GREEN 4 + REFACTOR

// GREEN
function validate(password) {
  if (password.length < 8) return false;
  if (!/[A-Z]/.test(password)) return false;
  if (!/[0-9]/.test(password)) return false;
  return true;
}

// REFACTOR — чище и читаемее
function validate(password) {
  const rules = [
    { test: (p) => p.length >= 8,     message: 'минимум 8 символов' },
    { test: (p) => /[A-Z]/.test(p),   message: 'нужна заглавная буква' },
    { test: (p) => /[0-9]/.test(p),   message: 'нужна цифра' },
  ];
  return rules.every(rule => rule.test(password));
}
// Все 4 теста проходят ✓

Три правила Uncle Bob

  1. Не пиши production-код без падающего теста
  2. Не пиши больше одного падающего теста за раз
  3. Не пиши больше кода, чем нужно для прохождения текущего теста

Ритм работы

Каждый цикл занимает 2-5 минут. За час — 12-30 циклов Red-Green-Refactor. Это не медленнее обычной разработки — ты просто не отлаживаешь часами потом.

Частые ошибки

  • Пропуск Red фазы — пишешь код до теста и тест сразу проходит; нет уверенности что тест вообще проверяет что нужно
  • Слишком большой Green шаг — пишешь сразу полную реализацию; маленькие шаги дают уверенность и контроль
  • Пропуск Refactor — код постепенно деградирует; рефакторинг под защитой тестов — главная польза TDD

Связанные темы

Ресурсы