Кажется, про TDD давно всё известно: сперва тест — потом код — получаем покрытие. Но на деле его суть понимают неправильно — как критики, так и сторонники.
Эта статья — не инструкция и не религиозная проповедь. Это разбор заблуждений. Причём речь пойдёт не только о критиках TDD, но и о его сторонниках.
TDD часто воспринимают как способ добиться максимального покрытия или как дисциплину «писать тесты вперёд». Но настоящая цель — не в тестах, а в итеративном проектировании поведения и архитектуры.
Если вам кажется, что TDD — это занудно, медленно, не подходит к реальному коду или убивает гибкость — возможно, вы всё делали правильно.
Но с совершенно другими целями.
Именно поэтому вам не понравилось.
Разберёмся, что такое TDD на самом деле — и почему вы, скорее всего, не знаете TDD.
1. Недопонимание цели TDD — главное заблуждение
Ошибка: TDD — это способ добиться тестового покрытия.
Реальность: TDD — это способ проектирования.
Чаще всего TDD воспринимают как технику написания тестов до кода, чтобы удостовериться, что всё протестировано. На практике же его основная цель — не сами тесты и не метрики покрытия, а итеративная разработка системы через формулирование желаемого поведения. Тесты здесь выполняют роль спецификаций, а не проверки. Разработчик получает не просто проверку работы, а возможность обдумать и уточнить интерфейсы, зависимости, структуру модулей. В результате сама разработка продукта становится более технологичной: проектирование и реализация сливаются в управляемый процесс, где каждое поведение вводится осознанно и поддаётся проверке.
2. Ожидание мгновенного повышения качества
Ошибка: Если писать по TDD, багов не будет.
Реальность: Качество — это не только тесты.
TDD действительно снижает количество дефектов — за счёт итеративности, простоты и чётких целей. Но само по себе написание тестов не заменяет мышление. Даже в рамках TDD можно писать плохие тесты, дублировать код или закреплять ошибки в логике. TDD — это инструмент, а не волшебная кнопка качества. Эффект от него зависит от зрелости подхода и понимания сути.
3. Использование TDD как догмы
Ошибка: TDD обязателен для любого проекта.
Реальность: TDD — инструмент, а не религия.
TDD подходит далеко не всегда: он работает лучше всего там, где можно чётко выразить поведение — бизнес-логику, правила, трансформации. В экспериментальном коде, UI, интеграциях с внешними сервисами TDD может быть неэффективен или даже мешать. Навязчивое следование методике без учёта контекста приводит к разочарованию.
4. Путаница с Unit Testing
Ошибка: TDD — это просто юнит-тесты.
Реальность: TDD — это процесс, а не тип тестирования.
Можно писать юнит-тесты без TDD. Можно применять TDD, используя end-to-end или интеграционные тесты, если они описывают поведение системы.
Можно даже вообще обойтись без автоматизированных тестов — формулировать ожидаемое поведение и проверять его вручную. Такой вариант, конечно, не является общепринятым и вызывает споры, поскольку нарушает привычную для многих автоматизированную природу TDD. Тем не менее, если сохраняется порядок действий и намерение — сначала убедиться, что поведение не реализовано, затем реализовать, затем убедиться, что работает, и только потом рефакторить — такой подход с определёнными допущениями можно считать разновидностью TDD.
Тип тестов зависит от уровня, на котором мы проектируем. Главное — не наличие теста как такового, а то, что именно он фиксирует и в каком контексте создаётся. Суть TDD — в порядке действий и в намерении.
5. Беспокойство о «медленности»
Ошибка: TDD замедляет разработку.
Реальность: TDD экономит время на отладке и переосмыслении.
На старте действительно может показаться, что TDD замедляет разработку: приходится писать тесты, продумывать интерфейсы, проводить рефакторинг. Но эти усилия окупаются уже в среднесрочной перспективе. Код становится проще, понятнее и устойчивее к изменениям. Возникает меньше багов, проще менять требования. TDD — это инвестиция.
К тому же, даже если вы не пишете тесты, вы все равно так или иначе тестируете создаваемое поведение, запуская программу после внесения в нее изменений. А это тоже требует времени, что дополнительно нивелирует разницу в трудозатратах.
6. Неправильное понимание цикла «тест → код → рефакторинг»
Ошибка: Сначала пишем тесты и реализацию целиком.
Реальность: Маленькие шаги позволяют проектировать поведение.
Многие нарушают ритм TDD, пытаясь сразу написать всё: тест и полноценную реализацию. Это приводит к переусложнению, пропущенному рефакторингу и неустойчивой архитектуре. Настоящий цикл TDD — это серия маленьких шагов: пишем минимальный тест, реализуем минимум для его прохождения, затем улучшаем структуру. Этот ритм помогает контролировать сложность и сохранять гибкость кода.
7. Написание тестов ради тестов
Ошибка: Тесты нужны ради покрытия.
Реальность: Тесты фиксируют поведение.
Когда тесты пишутся «на всякий случай» — ради галочки или ради отчёта — они не несут пользы. Более того, такие тесты часто ломаются от безобидных изменений и мешают развитию. В TDD тест — это прежде всего способ выразить, что система должна делать. Это инструмент мышления. Он фиксирует предположения о поведении — и помогает их проверять.
8. Игнорирование рефакторинга
Ошибка: Реализация — цель.
Реальность: Качественный код — часть процесса.
Часто после прохождения теста разработчик двигается дальше, забывая про третий шаг — рефакторинг. В итоге код начинает обрастать дублированием, усложняться, терять структуру. В TDD рефакторинг обязателен. Это не «посмотрим потом», а часть цикла. Именно в этом шаге рождается хорошая архитектура: без изменения поведения, но с улучшением формы.
9. Неподходящее применение TDD
Ошибка: TDD применимо везде.
Реальность: Некоторые задачи требуют других подходов.
TDD трудно применять там, где невозможно заранее определить поведение: например, в UI, в обработке нестабильных API, в исследовательском коде. Здесь либо тесты получаются слишком хрупкими, либо разработка идёт вслепую. В таких случаях лучше использовать другие инструменты: прототипирование, мануальное исследование, интеграционные тесты. TDD — не серебряная пуля.
10. Подмена проектирования тестами
Ошибка: Архитектура «вырисуется» сама собой.
Реальность: Высокоуровневый дизайн требует осознанного подхода.
Иногда можно услышать: «Я не продумываю архитектуру — я просто следую TDD, и всё складывается само». Это рискованный путь. TDD помогает проектировать детали — интерфейсы, взаимодействие компонентов. Но он не заменяет продуманного подхода к архитектуре системы, разграничению ответственности, выбору моделей. Без общей картины можно прийти к локально хорошему, но системно хрупкому решению.
TDD — не про тесты. Это про мышление. Про то, как маленькими шагами идти к поведению, которое можно формализовать, проверить и изменить. И если вы когда-то попробовали TDD и разочаровались — вполне возможно, вы всё делали правильно. Просто вы решали не те задачи.
Комментарии (14)
viordash
20.08.2025 08:16спасибо, хорошее раскрытие техники разработки ПО.
6. Неправильное понимание цикла «тест → код → рефакторинг»
я с годами вернулся к такой последовательности, код > тест > рефакторинг > ...
Тест, написанный первым получался довольно синтетическим и был практически бестолковым.
iv660 Автор
20.08.2025 08:16Ваша последовательность вполне имеет право на существование. Однако это не TDD.
Ваша методика обеспечивает хорошие тесты.
Для TDD не так важно, какие в результате получаются тесты. Гораздо важнее, какая получается реализация. Такая последовательность дает стимулы, чтобы реализация была минимально необходимой для закрытия имеющихся требований.
Суть TDD — «сформулировал требование → реализовал → привел к товарному виду». Тест — это всего лишь инструмент для фиксации требований и факта их закрытия реализацией.
Throwable
20.08.2025 08:16Реальность: TDD — это способ проектирования.
Ну вот ни разу. Тем более, что вы сами себя опровергли в п10. TDD к проектированию имеет такое же отношение как автомастерская к автопроизводителю. Простой, чистый и понятный код -- это и есть способ проектирования.
Реальность: TDD экономит время на отладке и переосмыслении.
TDD обнаруживает часть ошибок до того, как они будут репортированы клиентом. Тесты не инвестиция, а мусор, кодовая база которого может до 10х раз превышать бизнес код. Инвестиция -- функционал и качественный код, к которому TDD не имеет никакого отношения. Говнокодить на ура можно и по TDD.
Код становится проще, понятнее и устойчивее к изменениям. Возникает меньше багов, проще менять требования.
Вот эта основная херь, на которую ведутся миллионы. Все с точностью до наоборот. Мелкие итеративные шаги ведут к многократному покрытию одного фрагмента. Поэтому минимальное изменение требований функционала валит стопицот тестов, ковырять которые приходится долго и рутинно. Поэтому любой рефакторинг вообще кладется в долгий ящик, т.к. никому не уперлось возиться мегабайтами свалившихся тестов, при всем при том, что это лишь внутреннее архитектурное изменение, которое не трогает заявленный функционал.
Реальность: Маленькие шаги позволяют проектировать поведение.
Для того, чтобы построить небоскреб, нужно сложить кирпичи в стену и проверить ее на прочность. В следующей итерации подумаем что с этим делать. Проектирование поведения происходит в бизнес коде. Тесты -- для фиксирования поведения и проверки корректности (соответствии поведения заявленному контракту). А уж никак не для проектирования.
Вывод: пишите функциональные тесты, которые проверяют внешнее поведение (API, спецификацию, т.д.). Избегайте многократного покрытия. Проектируйте без TDD.
iv660 Автор
20.08.2025 08:16Понимаю ваши аргументы, но тут вопрос именно в терминах. Когда я пишу «TDD — это способ проектирования», речь не про «архитектурное проектирование» на уровне системы, а про итеративное проектирование поведения кода. Маленькие шаги в TDD — это не про «построить небоскреб кирпичами», а про уточнение интерфейсов и контрактов по мере их написания.
Что до «мусорных тестов» — да, при механическом следовании «пиши тесты вперед» легко получить горы ненужного кода. Но это не цель и не сущность TDD. Суть в том, чтобы тесты были инструментом мышления и проверки гипотез. Если тесты не помогают, а мешают — это уже не TDD, а просто неудачная реализация практики.
Поэтому возражение «проектируйте без TDD» работает только если понимать TDD исключительно как «наклепать кучу мелких юнит-тестов». Но в изначальном смысле TDD — это как раз дисциплина, которая помогает проектировать код так, чтобы он был удобен для изменения и расширения.
Throwable
20.08.2025 08:16Да, я понимаю. Но в этом и состоит основная проблема. Сначала вы пишите мелкие компоненты, тестируете каждый из них. Затем тестируете из связку. Затем добираетесь до функционального уровня, например API и тестируете уже его. В итоге функционал одного и того же компонента покрывается многократно, что затрудняет будущий рефакторинг и вообще любые изменения. При всем при том, что ваш внутренний дизайн никому особо не упал, важно, чтобы был соблюдён контракт на уровне API.
viordash
20.08.2025 08:16нужно сложить кирпичи в стену
вот как раз юнит-тест и гарантирует что ваш кирпич правильной формы, и он гарантировано встанет ровно и надежно вместе с другими кирпичами.
А зачем покрывать один и тот же функционал в разных тестах? Честно говоря не пойму для чего. Для меня, функциональное тестирование, бывает нужным, если в связке есть либа\система которая не вызывает доверия. А если все модули уже надежны, то функциональное тестирование это так сказать "шлифовка". Но предполагаю, что это все зависит от области применения.
ws233
20.08.2025 08:16Пирамида тестирования утверждает, что каждая единица поведения должна быть проверена ровно 1 раз и на том самом нижнем уровне, на котором её можно проверить. Не все можно проверить на самом нижнем уровне – уровне модульных тестов без интеграций. Но если уж вы проверили что-то на более низком уровне, то на более высоком дублирующих тестов быть не должно, это же очевидно.
Как результат, ситуация "функционал одного и того же компонента покрывается многократно" невозможна. Более того, если тесты пишутся именно на поведение, а не на детали внутренней реализации, то проблем с рефакторингом не будет. Почему, очень хорошо объяснено в книге Хорикова про модульное тестирование.
iv660 Автор
20.08.2025 08:16Вы сами очень точно сказали:
Тесты -- для фиксирования поведения и проверки корректности (соответствии поведения заявленному контракту).
Я бы уточнил: для фиксирования требований и проверки соответствия этим требованиям. Когда вы сначала формулируете требования, а только потом проектируете, — у вас есть все шансы этим требованиям соответствовать. В этом и состоит суть TDD.
ws233
20.08.2025 08:16Для конкретизации п.5 вот даже целую детальную статейку набросали, почему так выходит...
iv660 Автор
20.08.2025 08:16Спасибо за ссылку — хороший кейс в поддержку п.5. Статья говорит про модульные тесты (не про TDD напрямую), но выводы те же: ранняя фиксация требований и быстрый фидбек сокращают общий цикл.
fo_otman
Маленькие шаги - это вроде agile называется, не? Спринты по 2 недели, ценность, ретро, демо для этого и были придуманы, чтобы идти на ощупь в условиях создания чего-то нового, неизвестного и потому непонятного.
noavarice
Маленькое шаги - это принцип, agile можно считать применением этого принципа по отношению к организации разработки в команде. TDD можно считать применением того же принципа по отношению к организации работы отдельно взятого разработчика - разрабатываем продукт в команде, но в конечном счёте код пишет каждый отдельно
Neka_D
Agile буквально переводится как гибкость или маневренность. Все перечисленное было придумано чтобы снизить цикл обратной связи, а так же чтобы с каждой обратной связью улучшать процесс и продукт.
TDD идеально вписывается в этот концепт. Обратная связь моментальная и информативная и ты не движешься дальше вперёд, пока не обработаешь и не примешь во внимание обратную связь.
iv660 Автор
Вы точно подметили сходство.
Agile и TDD — это одна и та же идея, но на разных уровнях: Agile работает на уровне продукта и команды, а TDD — на уровне кода и архитектуры.