Где TDD окупается, а где нет

Что это такое

Форма цикла «красный — зелёный — рефакторинг» подходит одним задачам лучше, чем другим, и потому это инструмент, а не универсальное правило. Лучше всего он окупается там, где поведение укладывается в правило с входами и выходами и где вы уже примерно представляете, что именно строите. Хуже всего — на этапе исследования: проектирование ещё ищут, а интерфейс меняется каждые несколько минут. Умение состоит в том, чтобы понимать, чем именно вы заняты сейчас.

Почему это важно

Спорят сторонники и скептики обычно мимо друг друга: каждый представляет себе свою работу. Тот, кто строит движок расчёта цен, считает цикл естественным и не понимает, зачем ему сопротивляться; тот, кто прототипирует интерфейс, видит в нём смирительную рубашку и не понимает, зачем вообще возиться. Насчёт собственной задачи правы оба. Однако навязанный цикл там, где он мешает, даёт догматичный код и заброшенные наборы тестов, а пропущенный там, где он уместен, — непокрытую ветку, которая уносит ошибку в продакшен.

Ключевые идеи

Он окупается для логики с правилами

Идеальны бизнес-правила, расчёты, парсеры, конечные автоматы и алгоритмы: поведение описывается случаями, каждый случай становится тестом, а реализация прячется за стабильной границей. Сильнее всего это заметно на исправлении ошибок — напишите тест, воспроизводящий дефект, посмотрите, как он падает, и только потом правьте код. Такой тест остаётся единственным доказательством, что ошибка исчезла, и единственной защитой от её возвращения.

отчёт об ошибке -> тест, её воспроизводящий (красный) -> исправление (зелёный) -> больше не ломается

Он окупается там, где проектирование уже видно

Если вы знаете, как называется модуль и что он принимает, тест, написанный первым, не стоит ничего, а интерфейс вы получаете даром. Именно поэтому со второй и третьей функцией в модуле TDD идёт легко, а с первой — неуклюже. Первая функция и есть работа над проектированием, а она по сути исследование.

Он борется с вами во время исследования

Спайки, прототипы, компоновка интерфейса и вообще всё, что вы собираетесь выбросить, не стоит писать через тесты. Ценность спайка — в скорости, а будущего у такого кода нет. Дисциплина требует другого: признать спайк, а потом удалить его, и не дать одноразовому коду без тестов стать продуктом — именно так и рождается большая часть непокрытого кода.

спайк -> узнать -> удалить спайк -> написать настоящее, начиная с теста

Он неудобен для связующего кода и конфигурации

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

Тест раньше против теста после

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

Практика

Напишите тест раньше для ошибки

Возьмите дефект, который вы исправили недавно, или тот, о котором знаете, но ещё не исправили. Напишите тест, который его воспроизводит и падает, а затем почините код.

Готово, когда: тест падает до исправления и проходит после, а его название называет дефект.

Объявите намеренный спайк

Выберите очередную исследовательскую задачу из своего списка и заранее объявите её спайком: срок, никаких тестов, код после работы выбрасывается.

Готово, когда: спайк удалён, а сохранённая часть переписана через тесты быстрее, чем вы ожидали.

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

  • Цикл, навязанный спайку — исследование замедляется до черепашьего шага, а код всё равно идёт в мусор; исправить: объявите спайк, ограничьте его срок, удалите его.
  • Пропуск цикла для «очевидной» логики — крайний случай, о котором никто не подумал, уезжает в продакшен; исправить: применяйте цикл к правилам и расчётам и пишите тест, который падает, на каждую ошибку.
  • Модульные тесты для конфигурационной связки — они утверждают порядок вызовов и ломаются при каждом рефакторинге; исправить: покройте связку интеграционным тестом, который запускает систему.
  • Разговор про TDD, когда тесты пишут последними — обратная связь по проектированию потеряна, и ни один тест ни разу не падал; исправить: либо пишите тесты первыми, либо честно называйте это тестами после.
  • Покрытие как цель — высокое покрытие с пустыми утверждениями ничего не доказывает; исправить: смотрите, падают ли тесты, когда код ломается.

Что читать дальше