Как разрешить циклические зависимости для проектов, когда вы хотите удалить дублированный код?
Представьте, что у вас есть проекты:
- Project testUtils, который содержит несколько тестовых помощников для ваших проектов, все проекты зависят от него.
- Мобильный проект, который содержит MobileService и MobileController, который зависит от testUtils
- Проект 'customer', который содержит CustomerService и CustomerController, который зависит от мобильного телефона, а также testUtils
Вы создаете MobileServiceDouble, реализующий IMobileService в своем мобильном проекте, который проверяет зависимости при тестировании вашего MobileController.
Также вы хотите создать несколько тестов для вашего CustomerController. Этот класс зависит от MobileController. Вы решили не издеваться над MobileController. Вместо этого вы издеваетесь над зависимостями MobileController: MobileService. Вы создаете второй MobileServiceDouble, реализующий IMobileService.
Ваши тесты работают нормально, и вы решаете провести рефакторинг дублированного кода. Так, каков хороший способ устранить ваши дубликаты?
Вы не можете поместить MobileServiceDouble в свои testutils, потому что в противном случае у вас была бы круговая зависимость: mobile <-> testutils Ваш MobileServiceDouble реализует IMobileService, который находится в мобильном телефоне.
Вы можете переместить интерфейс для вашего MobileService. Но вы не хотите иметь какие-либо производственные интерфейсы в своем проекте testutil.
Я думаю, что это очень минимальный пример для реальной проблемы во многих системах, и я уверен, что есть отличные идеи, как ее решить.
Что ты предлагаешь?
Обновление 1: Я немного изменяю зависимости, чтобы указать, что это не проблема, потому что я решил использовать настоящий MobileController в своих тестах:
Представьте, что есть 3 пакета:
- Проект СервисРегистрация
- Project Mobile зависит от ServiceRegistry и TestUtils
- Заказчик проекта зависит от ServiceRegistry и TestUtils
- Project TestUtils
Mobile и Customer - это проекты, в которых есть модульные тесты. Таким образом, оба хотят макетировать, например, класс ServiceRegistryManager проекта ServiceRegistry.
В проектах Customer and Mobile создается ServiceRegistryManagerFakes. Тот же сценарий: как реорганизовать это дублирование? Я перемещаю поддельный класс в TestUtils.
Результатом является круговая зависимость. Пока я пишу, я думаю о проекте FakeTest, который может решить мою проблему:
На данный момент мои зависимости и мои проекты увеличились. Это кажется очень хрупким, потому что если моему ServiceRegistry нужен класс Fake, мне нужно создать еще один проект testFake2:
Если зависимость моего нового Project TestFakes2 добавляет зависимость к классу, который зависит от моей ServiceRegistry, тогда я снова столкнусь с проблемами.
Поэтому я всегда должен обращать внимание, когда я занимаюсь рефакторингом своих занятий. Мне пришлось бы создавать много пакетов только из-за циклических зависимостей. Моя цель - найти хорошую передовую практику, чтобы минимизировать мои усилия в отношении рефакторинга и зависимости проекта.
Ищете больше идей:).
1 ответ
Я думаю, что в вашем дизайне и архитектуре есть многочисленные запахи кода.
testUtils, кажется, ваши тесты содержат слишком сложную логику, если вам нужен отдельный модуль для хранения утилит, которые используются исключительно в ваших тестах. У вас есть юнит-тесты для ваших тестовых утилит?
Также вы хотите создать несколько тестов для вашего CustomerController. Этот класс зависит от MobileController. Вы решили не издеваться над MobileController. Вместо этого вы издеваетесь над зависимостями MobileController: MobileService.
Почему вы не хотите издеваться над MobileController? Ваш
CustomerController
следует использовать двойной тестMobileController
, Не следует делать предположение, чтоMobileController
использованияMobileService
, Это вводит связь междуCustomerController
Тест и мобильный сервис. Так что если однажды вы измените реализациюMobileController
(чтобы он больше не использовалMobileService
) без изменения контракта этого класса, тесты в другом модуле (customer
) начнет проваливаться.У ваших классов есть четко определенный контракт? Соответствуют ли они шаблону единой ответственности?
Обеспечиваете ли вы разделение интересов? Другими словами, есть ли у вас четкие правила, какая логика должна быть внутри контроллеров, а какая внутри сервисов? Если так, зачем вам использовать контроллер от другого контроллера?
Поскольку вы хотите смоделировать зависимости зависимости, я предполагаю, что вы пишете интеграционные тесты, которые увеличивают время цикла обратной связи по сравнению с модульными тестами (не говоря уже о других минусах).
Наконец, вы используете инъекцию зависимости?