awesome-everything EN
↑ Обратно к восхождению

Инженерная практика

Red-green-refactor — это цикл проектирования, а не ритуал тестирования

Суть Тест-сначала — это давление на дизайн, а не гигиена покрытия. Вы вызываете собственный API до того, как он существует, и плохой интерфейс всплывает за секунды, а не после пятнадцати мест вызова. Вся выгода живёт в шаге refactor, который команды режут под дедлайн.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на junior-высоте — поверхность
◷ 15 min

Прию просят добавить фичу «отмена заказа». Она пишет тест первым и сразу упирается в стену: чтобы проверить результат, ей надо собрать OrderService, которому нужны соединение с БД, платёжный шлюз, почтовый клиент и сервис фича-флагов — четыре коллаборатора, только чтобы протестировать одно булево правило. Тест невозможно написать чисто. Её коллега заметил бы это лишь после того, как cancel() уже вшит в контроллер, две джобы и админ-скрипт. Ненаписуемый тест — это и есть ревью дизайна: OrderService делает слишком много, и TDD сказал об этом до единой строки продакшен-кода.

Первый шаг — это давление на дизайн, а не покрытие

Цикл намеренно крошечный: написать наименьший падающий тест (red), написать наименьший код, делающий его зелёным (green), затем улучшить код, не меняя поведения (refactor). Ценность, которую большинство упускает, целиком живёт в шаге red. Написание теста до реализации заставляет вас вызывать собственный код снаружи до того, как он существует — вы выбираете имя функции, её аргументы, форму возврата и режим отказа, пока вы всё ещё потребитель, а не автор. Код, который неудобно вызывать, неудобно тестировать первым, поэтому плохой интерфейс всплывает за секунды, а не после того, как его вшили в пятнадцать мест вызова, которые теперь все надо менять.

Это переосмысляет всю практику. Люди спорят о TDD так, будто вопрос в том, «находит ли написание тестов первыми больше багов?». Это мимо. Механизм — это задержка обратной связи о дизайне: труднособираемый объект, метод, которому нужно шесть моков, функция, чей возврат нельзя проверить, не залезая во внутренности — это запахи дизайна, и тест-сначала вскрывает их в самый дешёвый момент, до того как дизайн прорастёт в кодовую базу. Сравнительный кейс-стади на arXiv показал, что эффект на дизайн был устойчивым сигналом во всех командах; эффект на число багов был шумнее.

Пропуск refactor — вот как цикл гниёт

Самый частый провал — остановиться на green. Под дедлайном ритуал схлопывается до «тест прошёл, выкатываем», и третий шаг — refactor — тихо исчезает. А это шаг, где убирается дублирование и чинятся запутанные имена пока тесты всё ещё стерегут поведение. Пропускайте его квартал — и у вас зелёный CI поверх кодовой базы, которую никто не хочет трогать. Зелёный — не здоровье. Прохождение тестов говорит, что поведение сохранено; оно ничего не говорит о том, осталась ли форма кода такой, которую человек может менять.

Здесь есть реальная цена, и притворяться иначе нечестно. Индустриальные данные оценивают тест-сначала примерно в 15–22% больше времени на входе, чем тест-потом для той же фичи. Размен — это смещённая вперёд обратная связь по дизайну и регрессионная сеть против последующих переделок — поэтому отдача TDD выше всего на долгоживущем, часто меняемом коде и слабее всего на одноразовых скриптах. Полностью разрешим это противоречие в уроке 04; пока суть в том, что шаг refactor — это где приземляется большая часть долгосрочной выгоды, и это первое, что команды срезают.

Шаг циклаЧто вы реально делаетеЧто он даётЦена пропуска
RedПишете наименьший падающий тест, вызывая API снаружиОбратную связь по дизайну до существования кода; плохой API валится здесьНаходите плохой API после того, как 15 мест вызова от него зависят
GreenПишете наименьший код, который проходит — пусть даже грубыйЗаставляет столкнуться с реальным требованием, а не с догадкойПереусложнение под случаи, которых не требует ни один тест
RefactorЧистите имена и дублирование, пока тесты зелёныеБо́льшую часть долгосрочной выгоды; безопасную реструктуризациюЗелёный CI поверх гниющего кода, который никто не тронет

Привязывайтесь к поведению, или набор станет обузой

Тихий убийца — это что вы проверяете. Тест, привязанный к поведению — «возврат за полностью отгруженный заказ отклоняется» — переживает любой рефакторинг, сохраняющий результат, поэтому он защищает вас, пока вы улучшаете код. Тест, привязанный к реализации — «RefundService вызывает inventoryClient.check() ровно раз, затем ledger.post()» — ломается в тот же миг, когда вы реорганизуете внутренности, даже если для клиента ничего не изменилось. Команда усваивает, что red обычно шум, и начинает его игнорировать. Именно так набор сползает из актива в обузу: это анти-паттерн хрупкого теста, описанный в xUnit Test Patterns.

Зеркальный провал так же опасен: тест, привязанный к реализации, может оставаться зелёным, пока реальное поведение сломано, потому что он лишь проверяет, что случился некий внутренний танец, а не что произведён правильный результат. Проверяйте через публичную поверхность, проверяйте наблюдаемые результаты и мокайте только настоящие внешние границы. Набор, который ломается ровно тогда, когда ломается поведение — и только тогда — это тот, которому разработчики доверяют достаточно, чтобы действовать.

Почему это работает

«Тест первым» звучит как дисциплина о корректности, но её настоящий продукт — это пригодный интерфейс. Когда вас заставляют потреблять собственный API до его написания, вы чувствуете трение, которое почувствуют ваши вызывающие — четыре зависимости в конструкторе, метод, который нельзя вызвать без живой БД, возврат, который нельзя проинспектировать. Эти трения — дефекты дизайна, и тест — первое место, где они становятся конкретными и дешёвыми для починки. Вы пишете не тест; вы проводите ревью дизайна, где вы сами — самый строгий пользователь.

Выбери лучший вариант

Вы пишете тест первым и обнаруживаете, что нужно собрать четыре коллаборатора (БД, оплата, почта, флаги), только чтобы прогнать одно правило отмены заказа. О чём говорит этот тест?

Викторина

Что в первую очередь даёт сеньору шаг «red» цикла red-green-refactor?

Викторина

Тест проверяет, что RefundService вызывает inventoryClient.check() раз, затем ledger.post() раз. Почему это анти-паттерн хрупкого теста?

Расставь шаги по порядку

Упорядочьте цикл red-green-refactor так, как его проводит сеньор для нового правила:

  1. 1 Red: пишете наименьший падающий тест, вызывая ещё не написанный API снаружи
  2. 2 Чувствуете трение — если объект трудно собрать, чините дизайн до продолжения
  3. 3 Green: пишете наименьший код, который делает тест зелёным
  4. 4 Refactor: чистите имена и убираете дублирование, пока тесты зелёные
  5. 5 Проверяете поведение, а не внутренности, чтобы тест пережил следующий рефакторинг
Вспомните перед уходом
  1. 01
    Коллега говорит: «TDD — это просто про поднятие покрытия, напишу тесты потом, результат тот же». Каков ответ сеньора?
  2. 02
    Почему «проверяй поведение, а не реализацию» решает, будет ли набор активом или обузой?
Итог

Red-green-refactor — это цикл проектирования, а не ритуал покрытия. Несущий шаг — red: написание теста первым делает вас первым потребителем собственного API, поэтому плохой интерфейс — объект, которому нужны четыре коллаборатора, метод, который не запустить без БД — валит тест за секунды, а не после того, как от него зависят пятнадцать мест вызова. Это обратная связь по дизайну в самый дешёвый момент, и сравнительный кейс-стади нашёл её самым устойчивым эффектом TDD. Шаг green заставляет столкнуться с реальным требованием; шаг refactor — где приземляется большая часть долгосрочной выгоды, ведь он убирает дублирование и чинит имена, пока тесты всё ещё стерегут поведение — и это первый шаг, который команды режут под дедлайн, оставляя зелёный CI поверх кода, который никто не тронет. Тест-сначала стоит примерно на 15–22% больше времени на входе, поэтому его отдача выше всего на долгоживущем коде и слабее всего на одноразовой работе. Главное — привязывайте тесты к поведению, никогда к реализации: тесты на реализацию ломаются на безобидных рефакторингах и могут оставаться зелёными, пока реальный результат сломан, приучая команду игнорировать red. Проверяйте наблюдаемые результаты через публичную поверхность — и набор сломается ровно тогда, когда сломается поведение.

Связанные уроки
Продолжить восхождение ↑Тестовые дублёры: London против Detroit и ловушка чрезмерного мокинга
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.