Правила Реализации Tdd В Старом Проекте

Статья «Постоянная ответственность шаблона репозитория» поднял ряд вопросов, на которые очень сложно ответить.

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

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

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

Продолжать можно бесконечно.

Для более полной картины я рассмотрел проблему абстракций с точки зрения их использования в готовом коде, в легаси-коде.

Репозиторий в данном случае нас интересует только как инструмент для достижения качественного и безошибочного кода.

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



Предисловие

Некоторые могут заметить, что в старом проекте невозможно применить TDD. Бытует мнение, что для них больше подходят разные виды интеграционных тестов (UI-тесты, end-to-end), т.к.

слишком сложно разобраться в старом коде.

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

Мне пришлось поработать над несколькими проектами, где ограничивались только интеграционными тестами, считая, что юнит-тесты не показательны.

При этом было написано очень много тестов, запущена куча сервисов и т. д. и т. п.

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

За свою практику мне удалось поработать в нескольких очень крупных проектах, где было много устаревшего кода.

У одних были тесты, другие только собирались это реализовать.

Мне самому удалось поднять 2 крупных проекта.

И везде я пытался так или иначе применить подход TDD. На начальных этапах понимания TDD воспринимался как разработка Test First. Но чем дальше мы шли, тем яснее становились различия между этим упрощенным пониманием и нынешней концепцией, кратко названной BDD. Какой бы язык ни использовался, основные положения, которые я назвал правилами, остаются неизменными.

Некоторые могут найти параллели между правилами и другими принципами написания хорошего кода.



Правило 1. Используйте метод «снизу вверх» (наизнанку).

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

На этом этапе вы контролируете как набор компонентов, так и будущую гибкость архитектуры.

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

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

Через некоторое время проект превращается в так называемый устаревший код. И вот здесь начинается веселье.

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

Другая сторона этой проблемы — объем работы, необходимый для выполнения такой задачи.

Поэтому в этом случае более эффективным будет подход снизу.

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

В этом случае вы можете ручаться за качество этого модуля, потому что.

он представляет собой законченную функциональную единицу.

Хочу отметить, что подходы не такие уж и простые.

Например, проектируя новый функционал в старой системе, вы волей-неволей будете использовать оба подхода.

При первичном анализе все равно нужно оценить систему, затем спуститься на уровень модуля, реализовать его, а затем вернуться на уровень всей системы.

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

Правило 2: Тестируйте только измененный код

При работе со старым проектом совершенно нет необходимости писать тесты для всех возможных сценариев работы метода/класса.

Более того, о некоторых сценариях вы можете вообще не знать, потому что.

их может быть очень много.

Проект уже в производстве, клиент доволен, так что можно расслабиться.

В общем, в такой системе только ваши изменения вызывают проблемы.

Поэтому тестировать следует только их.



Пример

Есть модуль интернет-магазина, который формирует корзину из выбранных товаров и сохраняет ее в базу данных.

Нас не волнует конкретная реализация.

Как это сделано, так это сделано — это устаревший код. Теперь нам нужно реализовать здесь новое поведение: отправлять уведомление в бухгалтерию, когда стоимость корзины превышает 1000 долларов.

Это код, который мы видим.

Как реализовать изменение?

  
  
  
   

public class EuropeShop : Shop { public override void CreateSale() { var items = LoadSelectedItemsFromDb(); var taxes = new EuropeTaxes(); var saleItems = items.Select(item => taxes.ApplyTaxes(item)).

ToList(); var cart = new Cart(); cart.Add(saleItems); taxes.ApplyTaxes(cart); SaveToDb(cart); } }

Согласно первому правилу, изменения должны быть минимальными и атомарными.

Нас не интересует загрузка данных, нас не интересует расчет налогов и сохранение их в базу данных.

Но нас интересует уже посчитанная корзина.

Если бы был модуль, который делал то, что нужно, то он выполнил бы требуемую задачу.

Вот почему мы это делаем.



public class EuropeShop : Shop { public override void CreateSale() { var items = LoadSelectedItemsFromDb(); var taxes = new EuropeTaxes(); var saleItems = items.Select(item => taxes.ApplyTaxes(item)).

ToList(); var cart = new Cart(); cart.Add(saleItems); taxes.ApplyTaxes(cart); // NEW FEATURE new EuropeShopNotifier().

Send(cart); SaveToDb(cart); } }

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

Именно об этом говорит второе правило.



Правило 3: Тестируйте только требования

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

Сначала напишите минимальный набор условий, которые можно представить как требования к модулю.

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

Подход BDD помогает направить ваш мозг на правильный путь.

Также представьте, как с ним будут взаимодействовать другие классы, являющиеся клиентами вашего модуля.

Им нужно написать 10 строк кода для настройки вашего модуля? Чем проще связь между частями системы, тем лучше.

Поэтому из старого кода лучше изолировать модули, отвечающие за что-то конкретное.

Здесь вам на помощь приходит SOLID.

Пример

Теперь посмотрим, как все, о чем я написал выше, поможет нам с кодом.

Для начала выделим все модули, которые имеют лишь косвенное отношение к созданию корзины.

Так распределяется ответственность по модулям.



public class EuropeShop : Shop { public override void CreateSale() { // 1) load from DB var items = LoadSelectedItemsFromDb(); // 2) Tax-object creates SaleItem and // 4) goes through items and apply taxes var taxes = new EuropeTaxes(); var saleItems = items.Select(item => taxes.ApplyTaxes(item)).

ToList(); // 3) creates a cart and 4) applies taxes var cart = new Cart(); cart.Add(saleItems); taxes.ApplyTaxes(cart); new EuropeShopNotifier().

Send(cart); // 4) store to DB SaveToDb(cart); } }

И вот как их можно отличить.

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

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

Это может помочь избавиться от сильной зависимости от него и использовать его в будущем как самодостаточный инструмент.

public class EuropeShop : Shop { public override void CreateSale() { // 1) extracted to a repository var itemsRepository = new ItemsRepository(); var items = itemsRepository.LoadSelectedItems();

Теги: #tdd #шаблон репозитория #solid #архитектура программного обеспечения #.

NET #tdd #ИТ-стандарты

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.