Инженерия ПО
TDD и BDD
Кент Бек, создатель TDD, рассказывал как однажды его попросили реализовать многовалютную финансовую систему. Вместо того чтобы проектировать архитектуру, он написал один тест: `expect(Money.dollar(5).plus(Money.franc(10))).equals(Money.dollar(10))`. Тест не компилировался. Но в нём была вся суть системы. Через 2 часа работы красно-зеленого цикла архитектура сформировалась сама - и она оказалась проще, чем любой вариант, который мог бы получиться при классическом проектировании.
- **Google:** внутренние исследования показали, что проекты с высокой тестовой культурой (включая TDD-практики) имеют значительно более низкий показатель production incidents
- **Pivotal/VMware:** весь продуктовый код пишется в паре и через TDD; это ключевая инженерная практика компании, не опциональная
- **NHS Digital (Великобритания):** перевод критических медицинских систем на BDD с Gherkin позволил клиническим специалистам проверять спецификации без технического посредника
Red-Green-Refactor цикл
TDD - это не про тестирование. Это про проектирование. Написание теста до кода заставляет думать об API, интерфейсах и контрактах прежде чем думать о реализации. Разработчик, пишущий тест первым, автоматически становится первым потребителем своего API - и сразу видит, удобно ли его использовать. Красный тест - это спецификация. Зеленый тест - это минимальная реализация. Рефакторинг - это чистота.
Три закона TDD (Robert C. Martin): 1) Нельзя писать production-код, пока нет падающего теста. 2) Нельзя писать больше теста, чем нужно для падения. 3) Нельзя писать больше production-кода, чем нужно для прохождения теста. Цикл: минуты, не часы. Не нужно планировать идеальную архитектуру заранее - она возникает через рефакторинг.
В TDD, зачем нужно убедиться, что тест сначала ПАДАЕТ (red) перед написанием реализации?
Given-When-Then и BDD
BDD - это TDD с другим словарем. Если TDD говорит 'красный-зеленый-рефакторинг', BDD говорит 'дано-когда-тогда'. Разница не техническая - она коммуникационная. Given-When-Then описывает поведение на языке, понятном не только разработчику, но и продуктовому менеджеру, QA и бизнесу. Спецификация становится исполняемой документацией.
Given-When-Then (GWT): Given - начальный контекст (preconditions), When - действие или событие, Then - ожидаемый результат. Gherkin - DSL для записи GWT сценариев в человекочитаемом виде. Cucumber (Java/Ruby), SpecFlow (.NET), Behave (Python) - фреймворки, выполняющие Gherkin-файлы. Jest/Vitest используют describe/it для неформального GWT.
В чём основная цель BDD по сравнению с обычным unit-тестированием?
Test-First проектирование
Test-first меняет направление силы: вместо 'написал код - добавил тест' появляется 'написал тест - код вырос из него'. Главное следствие - testable-by-design архитектура. Код, написанный test-first, как правило имеет меньше связей, четче разграниченные ответственности и более явные зависимости - потому что иначе его невозможно было бы протестировать изолированно.
Признаки тяжело тестируемого кода: скрытые зависимости (new Database() внутри функции вместо инъекции), глобальное состояние, смешивание бизнес-логики и I/O. Test-first обнаруживает эти проблемы немедленно - тест потребует инъекцию зависимостей. Это один из аргументов в пользу TDD: плохая архитектура создает боль при написании первого теста.
Разработчик пишет тест для нового класса и обнаруживает, что для создания объекта нужно передать 8 параметров. Что это сигнализирует с точки зрения TDD?
BDD инструменты: Vitest и Cucumber
Vitest и Jest предоставляют BDD-подобный синтаксис через describe/it прямо из коробки - это достаточно для большинства проектов. Cucumber с Gherkin-файлами нужен тогда, когда нетехнические участники команды (продакты, аналитики) должны писать или читать спецификации. Это двусторонний контракт: Cucumber требует дисциплины поддержания step definitions актуальными.
Vitest - современная альтернатива Jest для Vite-проектов: совместимый API, поддержка ESM, встроенная поддержка TypeScript, workspace для монорепо. Vitest UI - браузерный интерфейс для просмотра тестов. @cucumber/cucumber для Node.js: Gherkin сценарии -> step definitions -> assertions. Living documentation через Cucumber HTML Reports.
TDD замедляет разработку, потому что нужно писать вдвое больше кода
TDD замедляет начало, но ускоряет итерации: меньше дебага, более безопасный рефакторинг, меньше регрессий
Исследования (Microsoft Research, Google) показывают: TDD увеличивает время написания на 15-35%, но снижает количество дефектов на 40-90%. Экономия на дебаге и hotfix'ах с лихвой компенсирует начальные инвестиции уже на масштабе в несколько итераций.
Когда стоит использовать Cucumber с Gherkin вместо просто Jest/Vitest с describe/it?
Ключевые идеи
- **Red-Green-Refactor** - не про тесты, а про проектирование; тест до кода делает API удобным для потребителя
- **Given-When-Then** - универсальный язык между бизнесом и разработкой; описывает поведение, а не реализацию
- **Test-first** выявляет архитектурные проблемы немедленно: тяжело тестируемый код - признак плохой архитектуры
- **Cucumber vs Jest/Vitest** - выбирать по реальной потребности; Gherkin нужен только если нетехнические участники читают спецификации
Связанные темы
TDD и BDD определяют ритм всей инженерной практики:
- Integration и E2E Testing — TDD применяется на уровне unit; BDD-сценарии часто реализуются как E2E тесты
- Рефакторинг и чистый код — Тестовое покрытие - главный enabler безопасного рефакторинга
Вопросы для размышления
- Если TDD улучшает дизайн кода, почему многие опытные разработчики избегают его на практике? Что является реальным барьером?
- Как применять TDD к системам с высокой долей I/O (HTTP-запросы, файловая система, очереди сообщений)? Где граница между мокированием и реальным тестированием?
- BDD-спецификации на Gherkin могут устаревать, если нет дисциплины их поддержания. Как организовать процесс, чтобы living documentation оставалась живой?