Стиль Delphi: как структурировать модули данных для кода с возможностью тестирования?

Я ищу несколько советов о структурировании программ Delphi для удобства обслуживания. Я пришел к программированию Delphi через пару десятилетий, главным образом, C/С++, хотя я сначала научился программировать с Turbo Pascal, поэтому мне не неудобно с основным языком. В моем предыдущем опыте работы с С++ и С# я стал преобразованием TDD с помощью cxxtest и NUnit.

Я унаследовал эту программу, которую я теперь отвечаю за сохранение. Он состоит в основном из форм и нескольких модулей данных. Бизнес-логика приложения и доступ к данным в основном разбросаны по формам, а модули данных - это в основном просто места для жизни глобальных объектов ADO. Доступ к базе данных обычно выполняется путем обращения к глобальному экземпляру TADOQuery или TADOCommand, форматированию текста SQL прямо в соответствующее свойство объекта и вызове его метода Open или Execute.

Я пытаюсь привести бизнес-логику в степень инкапсуляции, где она может быть проверена на единицу. Я видел этот ответ, и это имеет смысл, поскольку абстрагирует логику от форм. Мне интересно, что лучше всего подходит для доступа к данным. Я считаю, что модули данных должны выставлять своего рода мини-API для приложений (возможно, со всеми виртуальными методами), чтобы их можно было заменить макетными объектами для тестирования. Ссылка на этот другой ответ показывает некоторые примеры, которые заставляют меня поверить, что я на правильном пути, но меня все еще интересует какой-то документ, о модулях данных. Большинство страниц, которые я могу найти через Google, представляют те же самые примеры обо всех интересных вещах, которые вы можете делать во время разработки, с подключением элементов управления, связанных с данными, к запросам и тому подобному, что меня не очень интересует на данный момент.

Ответ 1

Лично я не поклонник TDataModule. Это очень мало для поощрения хороших принципов проектирования OO. Если бы все это было использовано, это был удобный контейнер для компонентов БД, который был бы одним, но слишком часто он становился свалкой для бизнес-логики, которая была бы лучше в доменном слое. Когда это происходит, оно становится божественным классом и магнитом зависимости.

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

Мое предложение

  • Добавить уровень домена между вашим пользовательским интерфейсом и вашей базой данных
  • Нажимайте как можно больше своей бизнес-логики на объекты домена.
  • Сделайте свой пользовательский интерфейс и ваши уровни сохранения данных максимально возможными, используя design и архитектурные, чтобы делегировать процесс принятия решений на уровне домена.

Если вы не знакомы с этим, этот метод называется проектом, управляемым доменом. Это, конечно, не единственное решение, а хорошее. Основная предпосылка заключается в том, что пользовательский интерфейс, бизнес-логика и база данных меняются с разной скоростью и по разным причинам. Поэтому сделайте бизнес-логику моделью проблемной области и сохраните ее отдельно от пользовательского интерфейса и базы данных.

Как это делает мой код более подверженным тестированию?

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

Это стиль Delphi?

Это зависит от вашей перспективы. Традиционно большинство приложений Delphi создавались путем создания пользовательского интерфейса и базы данных в тандеме. Отбросьте несколько элементов управления db в дизайнере форм. Добавить/обновить таблицу с полями для хранения данных управления. Посыпать либеральным количеством бизнес-логики с помощью обработчиков событий. Виола! Вы просто испекли выражение. Для очень маленьких приложений это отличная экономия времени. Но не позволяйте себе малыша, небольшие приложения, как правило, превращаются в большие, и этот дизайн становится неустойчивым кошмаром в обслуживании.

Это действительно не ошибка языка. Вы найдете те же быстрые/грязные/близорукие проекты сотен магазинов VB, С# и Java. Такие приложения являются результатом начинающих разработчиков, которые не знают ничего лучшего (и опытных разработчиков, которые должны знать лучше), IDE, что делает его настолько легким и удобным, чтобы быстро выполнить работу.

В сообществе Delphi есть люди (как и в других сообществах), которые долгое время выступали за лучшие методы проектирования.

Ответ 2

Я думаю, вам нужно (и на самом деле, большинству разработчиков баз данных Delphi понадобится) компонент Mock Dataset (Query, table и т.д. и т.д.), который вы могли бы использовать, и заменять их на время init-модуля, для вашего текущего ADO наборы данных для этого макета набора данных для целей тестирования. Вместо того, чтобы форсировать интерфейсы в вашей конструкции, которые являются одним из способов обеспечения возможности замены, учтите тот факт, что по принципу подстановки Liskov вы должны иметь возможность (при испытании времени установки прибора), вводить в свой модуль данных, набор макетов -datasets, которые вы хотите использовать, и просто заменяйте используемые наборы данных ADO, которые вы используете, во время выполнения теста, с каким-либо другим функционально эквивалентным объектом (набор данных с макетами или набор данных таблицы с файловой поддержкой).

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

Поскольку вы не записывали набор данных ADO, вам не нужно unit test его. Однако издеваться над таким набором данных может быть сложно.

Я бы посоветовал вам рассмотреть возможность использования набора JvCsvDataSet или ClientDataSet в качестве основы для ваших наборов данных fxture. Затем вы сможете использовать их, чтобы убедиться, что все зависимые от платформы базы данных (материал, который записывает удаленные процедуры или базу данных SQL) абстрагируются от других классов, что опять же вам придется макетировать. Такое усилие может потребоваться не только для того, чтобы сделать ваш блок бизнес-логики поддающимся проверке, но также может стать шагом к созданию дружественной к базе данных платформы в вашей бизнес-логике.

Представьте, что у вас есть ADOQuery под названием CustomerQuery, переименовать объект, который вы удалили в свой модуль данных, в CustomerQueryImpl и добавить это в объявление класса модуля данных:

  private
        FCustomerQuery:TADOQuery;

  published
        property CustomerQuery:TADOQuery read FCustomerQuery write FCustomerQuery;

то в вашем модуле данных при создании события подключите свойство к объектам:

   FCustomerQuery := CustomerQueryImpl

Теперь вы можете написать модульные тесты, которые будут "подключаться" и заменять CustomerQuery собственным тестовым устройством (макетным объектом) во время выполнения.

Ответ 3

Во-первых, прежде чем менять что-либо, вам потребуются некоторые модульные тесты, чтобы вы могли убедиться, что вы ничего не сломаете. Я попытался бы написать единичные тесты против текущего графического интерфейса, не меняя ничего. DUnit поддерживает тестирование графического интерфейса (наряду с традиционными модульное тестирование), и хотя он немного неуклюж и не может обрабатывать модальные диалоги, он функциональный.

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

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

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

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

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

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

Ответ 4

Пожалуйста, прочитайте эту статью, ее об Единичном тестировании и Макетных объектах, включая теорию макетных объектов, локализацию UT и обнаружение интерфейсов.

надеюсь, вам понравится.