Почему изучать TDD трудно и что с этим делать. Часть 2

в 5:11, , рубрики: tdd, Программирование

Продолжение. Начало здесь.

Как все это использовать?

Хороший вопрос. Мы остановились на том, что TDD помогает четко определить границы текущей задачи, дает простой способ одновременной работы с мелкими деталями, относящимися к проблеме, и предоставляет быструю обратную связь с кодом, сообщая, насколько удачно получившееся решение. Именно эти факты помогут нам преодолеть трудности в изучении этой техники.

Какие тесты писать?

Самый простой (хотя и точный) ответ – делайте любые тесты, дающие возможность оценивать классы и проектировать дальше. Но этот ответ плохо подходит для начинающих, поэтому придется углубиться в детали.

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

Посмотрим на более выраженный случай. Скажем, мы имеем совершенно новый проект. Значит, мы даже еще не знаем, какие классы будут нам нужны. Как использовать TDD, чтобы что-то узнать о будущей структуре классов, когда еще нет вообще ни одного класса? Напишем тест, описывающий разрабатываемый функционал (или сценарий). Это будет скорее приемочный, нежели юнит-тест. Нигде не написано, что TDD основывается исключительно на юнит-тестах, но большинство новичков думают, что процесс основан именно на них, и из этого вытекает дальнейшее непонимание. Так вот, такой подход позволяет создать базовый скелет приложения, который в дальнейшем будет развиваться и обрастать плотью. Тесты позволят оценивать, насколько хорошо мы сумели разделить требуемый функционал на контролируемые, тестируемые абстракции. Вдобавок мы получим отличный интерфейс к системе, который пригодится в будущем при создании интеграционных тестов. Эта книга является отличным источником примеров (по ссылке — книга Growing Object-Oriented Software Guided by Tests, посвященная так называемой Лондонской школе TDD, придерживающейся подхода «снаружи-внутрь» с интенсивным использованием моков для еще нереализованных классов — прим. перев.)

Как только мы определили базовое поведение функционала посредством приемочного теста, мы создаем инфраструктуру, требуемую для теста (построения, непрерывная интеграция), и переходим к более конкретным тестам для выделения внутренних классов приложения. Возможно, это не понадобиться – направления, указанного приемочными тестами, будет достаточно для реализации функционала.

Но ты только что написал код, не создав сначала теста!

Запросто. TDD обеспечивает обратную связь с кодом. Когда вы уже знаете, что именно нужно (например, если работа заключается в дополнении мелкими частями готовой архитектуры), та часть TDD, что отвечает за проектирование, не дает много пользы. Единственное преимущество предварительного написания тестов здесь в том, что можно убедиться, что изначально они не срабатывают; это способ протестировать сами тесты. В этом случае подход «сначала тесты» является техникой создания кода, покрытого тестами, а не проектирования.

Чем меньше неизвестных, тем меньше нужно тестов, чтобы их найти и тем большими шагами можно двигаться.

Вот опять — используя паттерн MVVM, ты создал представление без предварительного теста!

Мы уже узнали, что TDD ограничивает текущую задачу, так же как и дает возможность понять, насколько хорошо ее решение. Конечно, TDD – далеко не единственный способ получить как первое, так и второе. Технологии создания представлений (WPF, MVC-фреймворки, GTK, WebForms и т.д.) сами по себе налагают существенные ограничения на код в части взаимодействия с UI.

При использовании паттернов выделения представления, таких как MVVM, мы можем определить необходимые свойства и команды у ViewModel вообще без тестов. Мы ограничены выбранной технологией построения интерфейса, а так же внешним дизайном, проработанным в ходе обсуждения (которое также является инструментом, обеспечивающим быструю обратную связь) с заказчиком.

Итак, TDD не является единственным способом ограничить задачу и получить оценку решения. Там, где существуют реальные условия, не следует игнорировать их и придумывать искусственные.

Примечание: Я столкнулся с трудностями при попытке воссоздать паттерны представления при помощи следования принципам TDD: как можно, используя только тесты, в итоге получить MVP, MVVM и т.д? Думаю, что это сложно из-за того, что с одной стороны, у нас есть абсолютно реальный UI-инструментарий, а с другой — только воображение и тесты. Существуют факторы, которые не вытекают из тестов, но являются такими же важными источниками информации. Если кому-то удалось получить в чистом виде паттерн отделения представления только при помощи TDD, пожалуйста, дайте знать.

Я застрял! Застрял в тестах и не знаю, что делать...

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

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

На заметку: Если не удалось решить проблему с помощью TDD, сделайте пометку об этом перед тем, как пробовать что-то еще. Вам важно понять, были ли эта задача в принципе не решаема с помощью TDD, или вам просто не хватило знаний. Уловить разницу не получится, если сдаться слишком быстро. Лично меня следование этому принципу привело в свое время к открытию таких штук как мокирование, IoC-контейнеры, соглашения, BDD и т.д. (и я все равно еще далек от полного отсутствия пробелов в знаниях).

TDD? BDD? ATDD? Что именно мне нужно?

Вы можете заметить, что использование TDD специфично для каждой задачи (и для каждого разработчика). Разнообразные задачи создают разные проблемы проектирования и требуют разнообразных видов обратной связи. Это значит, что нам следует задействовать TDD разными способами, чтобы получать разные оценки разрабатываемого кода и использовать их в развитии архитектуры. Такими способами могут быть «сверху внизу» или «снизу вверх». Иногда мы можем опираться большей частью на приемочные тесты. Иногда лучше использовать большое количество модульных тестов. Некоторые проблемы хорошо решаются с помощью тестов «от края до края», с использованием реальных внешних баз данных или сервисов (у этого подхода есть свои тонкости, но если он дает необходимую обратную связь и обеспечивает развитие архитектуры — используйте его).

В общем, здесь нет единственного верного ответа. Главная цель — получение обратной связи с кодом, над которым идет работа в данный момент.

Заключение

Хотя инструкции процесса TDD просты, сама техника таковой не является. Инструкции обеспечивают лишь самую минимальную помощь на старте, в то время как шумиха вокруг TDD формирует совершенно нереалистичные ожидания.

Мы можем решить эту проблему, пытаясь понять, как работает TDD и увидеть идею, стоящую за алгоритмом. Мы видим, что TDD на самом деле является инструментом для ограничения задачи, инкапсулирования процесса дизайна с помощью абстрактного вопроса «каким будет следующий тест», и предоставления возможности быстро оценивать результаты наших изысканий. По существу, TDD дает отличный способ продумывать и итеративно развивать дизайн приложения.

Поняв это, мы можем преднамеренно использовать TDD для получения обратной связи с разрабатываемым кодом и использования этой информации для решения задач проектирования. Мы не ограничиваемся только юнит-тестами и постоянно переключаемся между уровнями абстракции тестов (чего совсем нет в демонстрационных примерах использования TDD). И мы запросто обходимся без TDD там, где есть другие способы получения обратной связи с кодом, такие как фреймворки отделения UI или технологии хранения данных.

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

Проектировать всегда трудно. TDD позволяет сконцентрироваться на дизайне и понять, как сделать его лучше.

Надеюсь, эти размышления откроют Вам иной взгляд на TDD и сделают процесс изучения этой техники более простым. TDD стоит затраченных усилий. Желаю удачи!

Автор: velvetcat

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js