Стиль Delphi: Как структурировать модули данных для тестируемого кода?
Я ищу несколько советов по структурированию Delphi-программ для удобства обслуживания. Я пришел к программированию на Delphi после пары десятилетий, в основном на C/C++, хотя я впервые научился программировать с Turbo Pascal, поэтому я не чувствую себя неловко с базовым языком. В моем предыдущем опыте работы с C++ и C# я стал преобразовывать TDD, используя cxxtest и NUnit.
Я унаследовал эту программу, за которую теперь отвечаю. Он состоит в основном из форм и пары модулей данных. Бизнес-логика приложения и доступ к данным в основном разбросаны по формам, а модули данных - это всего лишь места, где могут жить глобальные объекты ADO. Доступ к базе данных обычно осуществляется путем обращения к глобальному экземпляру TADOQuery или TADOCommand, форматирования текста SQL прямо в соответствующем свойстве объекта и вызова его метода Open или Execute.
Я пытаюсь привести бизнес-логику в степень инкапсуляции, где она может быть проверена модулем. Я видел этот ответ, и он имеет смысл, если абстрагировать логику от форм. Мне интересно, каковы лучшие практики для доступа к данным. Я думаю, что модули данных должны предоставлять своего рода мини-API для приложений (возможно, со всеми виртуальными методами), чтобы их можно было заменить на фиктивные объекты для тестирования. Ссылка на этот другой ответ показывает некоторые примеры, которые наводят меня на мысль, что я на правильном пути, но я все еще заинтересован в том, чтобы увидеть какой-нибудь документ с рекомендациями по модулям данных. Большинство страниц, которые я могу найти через Google, представляют собой примеры того же рода обо всех интересных вещах, которые вы можете делать во время разработки, подключая привязанные к данным элементы управления к запросам и тому подобные вещи, которые меня не очень интересуют. в данный момент.
4 ответа
Лично я не фанат TDataModule. Это очень мало для поощрения хороших принципов проектирования ОО. Если бы все это использовалось для создания удобного контейнера для компонентов БД, это было бы одно, но слишком часто это превращалось в дамп для бизнес-логики, которая была бы лучше на уровне домена. Когда это происходит, оно становится классом богов и магнитом зависимости.
Добавьте к этому баг (или, возможно, его особенность), который продолжает существовать, по крайней мере, с Delphi 2, из-за которого элементы управления, осведомленные о данных формы, теряют свои источники данных, если эти источники данных расположены в модуле, который не был открыт перед формой.,
Мое предложение
- Добавьте слой домена между вашим пользовательским интерфейсом и базой данных
- Вставьте как можно больше своей бизнес-логики в доменные объекты.
- Сделайте свой пользовательский интерфейс и свои уровни персистентности данных как можно более мелкими, используя шаблоны проектирования и архитектуры для делегирования принятия решений на уровне домена.
Если вы не знакомы с этим, то этот метод называется доменным дизайном. Это, конечно, не единственное решение, но хорошее. Основная предпосылка заключается в том, что пользовательский интерфейс, бизнес-логика и база данных меняются с разной скоростью и по разным причинам. Поэтому сделайте бизнес-логику моделью проблемной области и отделите ее от пользовательского интерфейса и базы данных.
Как это делает мой код более тестируемым?
Переместив бизнес-логику на ее собственный уровень, вы можете протестировать ее без вмешательства пользователя или базы данных. Это не означает, что ваш код будет по сути тестируемым просто потому, что вы поместите его в свой собственный слой. Создание тестируемого устаревшего кода - сложная задача. Большая часть устаревшего кода тесно связана, поэтому вы потратите немало времени, разбирая его на классы с четко определенными обязанностями.
Это стиль Delphi?
Это зависит от вашей точки зрения. Традиционно большинство приложений Delphi создавались путем разработки пользовательского интерфейса и базы данных в тандеме. Удалите несколько элементов управления с поддержкой БД в конструкторе форм. Добавить / обновить таблицу с полями для хранения данных элемента управления. Посыпать либеральным количеством бизнес-логики, используя обработчики событий. Виола! Вы только что испекли заявку. Для очень маленьких приложений это отличная экономия времени. Но давайте не будем обманывать себя, маленькие приложения имеют тенденцию превращаться в большие, и этот дизайн становится непреодолимым кошмаром обслуживания.
Это действительно не вина языка. Вы найдете такие же быстрые / грязные / близорукие дизайны из сотен магазинов VB, C# и Java. Подобные приложения являются результатом работы начинающих разработчиков, которые не знают ничего лучше (и опытных разработчиков, которые должны знать лучше), IDE, которая делает ее такой простой и требует быстрого выполнения работы.
В сообществе Delphi (как и в других сообществах) есть такие, которые долгое время отстаивали лучшие методы проектирования.
Я думаю, что вам нужен (и на самом деле большинству разработчиков баз данных Delphi понадобится) компонент Mock Dataset (Query, table, etc и т. Д.), Который вы могли бы использовать, и заменить его во время инициализации модуля для ваших текущих объектов набора данных ADO к этому фиктивному набору данных, для целей тестирования. Вместо того чтобы встраивать интерфейсы в свой дизайн, которые являются одним из способов обеспечения возможности замещения, следует учитывать тот факт, что по принципу подстановки Лискова вы сможете (во время настройки тестового устройства) внедрить в свой модуль данных набор макетов -наборы данных, которые вы хотите использовать, и просто замените наборы данных ADO, которые вы используете, во время выполнения теста, на какую-то другую функционально эквивалентную сущность (фиктивный набор данных или набор табличных данных на основе файла).
Возможно, вы могли бы даже полностью удалить наборы данных из модуля данных и подключить их во время выполнения (в вашем главном приложении) к нужным объектам набора данных ADO, а в модульных тестах присоединить наборы фиктивных данных.
Поскольку вы не написали набор данных ADO, вам не нужно его тестировать. Однако создание такого набора данных может быть затруднено.
Я бы посоветовал вам рассмотреть возможность использования JvCsvDataSet или ClientDataSet в качестве основы для ваших наборов данных фикстур (фиктивных). Затем вы сможете использовать их, чтобы удостовериться, что все зависимости вашей платформы базы данных (вещи, которые пишут удаленные процедуры или SQL базы данных) абстрагированы в другие классы, которые вам снова придется макетировать. Такие усилия могут потребоваться не только для того, чтобы сделать ваш модуль бизнес-логики тестируемым, но также могут стать шагом к тому, чтобы стать более дружественными к нескольким базам данных в вашей бизнес-логике.
представьте, что у вас есть ADOQuery с именем CustomerQuery, переименуйте объект, который вы перетащили в свой модуль данных, в CustomerQueryImpl и добавьте его в объявление класса вашего модуля данных:
private
FCustomerQuery:TADOQuery;
published
property CustomerQuery:TADOQuery read FCustomerQuery write FCustomerQuery;
затем в вашем модуле данных при создании события подключите свойство к объектам:
FCustomerQuery := CustomerQueryImpl
Теперь вы можете написать модульные тесты, которые будут "зацеплять" и заменять CustomerQuery своим собственным тестовым прибором (фиктивным объектом) во время выполнения.
Во-первых, перед тем, как что-то изменить, вам нужно провести несколько юнит-тестов, чтобы убедиться, что вы ничего не сломаете. Я бы попытался написать модульные тесты против текущего GUI, ничего не меняя. DUnit имеет поддержку для тестирования GUI (наряду с традиционным модульным тестированием), и хотя он немного неуклюж и не может обрабатывать модальные диалоги, он функционален.
Далее, поскольку в ваших формах не используются элементы управления, учитывающие данные, я бы подошел к этому, введя еще один уровень модулей данных, если хотите, уровень обслуживания, между формами и существующими глобальными модулями данных.
Для каждой формы в вашем приложении я бы создал соответствующий новый модуль данных уровня обслуживания. Это может показаться большим количеством модулей данных, но они очень легкие, и вы можете объединить их позже, если хотите.
Вы можете использовать обычные объекты TObject, а не TDataModules для уровня обслуживания, если вам нравится, однако использование модулей данных дает вам возможность размещать на них невизуальные компоненты, например, TClientDataSet и TDataSource, если вы отключили поддержку данных контролирует маршрут на более поздний срок.
Первоначально каждый модуль данных уровня обслуживания просто действовал бы как прокси для доступа к глобальным модулям данных. На данный момент ваша цель состоит в том, чтобы просто удалить прямую зависимость форм от глобальных модулей данных.
Как только формы только косвенно обращаются к глобальным модулям данных через модули данных уровня обслуживания, я начинаю перемещать функциональность из форм в уровень обслуживания. С этой функциональностью в модулях данных сервисного уровня вам будет гораздо проще писать модульные тесты для нового и существующего кода.
На этом этапе вы также можете начать консолидацию модулей данных уровня обслуживания для каждой формы. Теперь будет гораздо проще консолидировать их после завершения логического извлечения из форм, чем если бы вы попытались сделать это во время этого процесса.
Пожалуйста, прочтите эту статью о модульном тестировании и фиктивных объектах, включая теорию фиктивных объектов, локализацию UT и обнаружение интерфейсов.
Надеюсь, тебе понравится.