Стоит ли внедрение зависимостей за пределы UnitTesting?

17

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

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

Seph
источник
3
ПОЦЕЛУЙ - это всегда лучший подход.
Reactgular
5
На мой взгляд, этот вопрос более конкретен, чем предлагаемый дубликат. дизайн-для-будущих-изменений-или-решить-проблему-под рукой , поэтому я голосую, чтобы не закрывать это
k3b
1
Разве тестируемость не является достаточной причиной?
ConditionRacer
На самом деле не похоже на дубликат, а на самом деле отвечает сам на себя. Если это никогда не произойдет, дизайн для него не будет проектироваться для будущих изменений. Даже астронавты архитектуры только проектируют изменения, которые никогда не произойдут из-за неправильного суждения.
Steve314

Ответы:

13

Это зависит от того, насколько вы уверены, что «никогда, никогда» правильно (во время использования вашего приложения).

В общем:

  • Не изменяйте свой дизайн только для тестируемости. Модульные тесты будут служить вам, а не наоборот.
  • Не повторяйся. Создание интерфейса, который будет иметь только одного наследника, бесполезно.
  • Если класс не тестируемый, он, вероятно, не будет гибким / расширяемым для реальных случаев использования. Иногда это хорошо - вам вряд ли понадобится такая гибкость, но простота / надежность / ремонтопригодность / производительность / все, что важнее.
  • Если вы не знаете, код интерфейса. Требования меняются.
Telastyn
источник
12
Почти дал вам -1 за «не изменяйте свой дизайн только для тестируемости». ИМХО, чтобы получить тестируемость, является одной из главных причин для изменения дизайна! Но остальные ваши пункты в порядке.
Док Браун
5
@docBrown - (и другие) Я здесь отличаюсь от многих, и, возможно, даже от большинства. ~ 95% времени, делая вещи тестируемыми, также делает их гибкими или расширяемыми. Вы должны иметь это в своем дизайне (или добавить это к своему дизайну) большую часть времени - быть проклятым. Остальные ~ 5% либо имеют странные требования, которые препятствуют такого рода гибкости в пользу чего-то другого, либо настолько важны / неизменны, что вы довольно быстро узнаете, сломался ли он, даже без специальных тестов.
Теластин
4
может быть и уверен, но ваше первое утверждение, приведенное выше, может быть слишком легко неверно истолковано как «оставить плохо спроектированный код, как это бывает, когда ваша единственная задача - тестируемость».
Док Браун
1
Почему вы говорите: «Создание интерфейса, в котором будет только один наследник, бесполезно».? Интерфейс может работать как контракт.
Каптан
2
@kaptan - потому что конкретный объект может работать так же хорошо, как контракт. Создание двух означает, что вам нужно написать код дваждыдважды изменить код при его изменении). Конечно, подавляющее большинство интерфейсов будет иметь несколько (не тестовых) реализаций, или вам нужно подготовиться к такой возможности. Но для того редкого случая, когда все, что вам нужно, это простой запечатанный объект, просто используйте его напрямую.
Теластин
12

Оправданы ли мы, избегая попыток запрограммировать интерфейс?

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

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

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

YAGNI предполагает, что при написании кода важно не добавлять ничего лишнего. Одна из вещей, которую программисты склонны добавлять в свой код, даже не подозревая об этом, - это лишние предположения. Как «этот метод может пригодиться» или «это никогда не изменится». Оба добавляют беспорядок в ваш дизайн.

back2dos
источник
2
+1 за цитирование ЯГНИ в качестве аргумента для ДИ, а не против этого.
Док Браун
4
@DocBrown: Учитывая , что YAGNI может быть использован здесь, также обосновать не используя DI. Я думаю, что это скорее аргумент против того, что YAGNI - полезная мера для чего-либо.
Стивен Эверс
1
+1 за лишние предположения, включенные в YAGNI, которые несколько раз ударили нас по голове
13
@ SteveEvers: Я думаю, что использование YAGNI для оправдания игнорирования принципа инверсии зависимостей предотвращает отсутствие понимания YAGNI или DIP или, возможно, даже некоторых из более фундаментальных принципов программирования и рассуждений. Вы должны игнорировать DIP, только если вам нужно - это обстоятельство, при котором YAGNI просто не применим по самой своей природе.
back2dos
@ back2dos: предположение, что DI полезен из-за «возможно-требований» и «кто знает?» это именно тот ход мыслей, что YAGNI предназначен для борьбы, вместо этого вы используете его как оправдание для дополнительных инструментов и инфраструктуры.
Стивен Эверс
7

Это зависит от различных параметров, но вот несколько причин, почему вы все еще можете использовать DI:

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

Итог: в этом случае вы, вероятно, можете жить без DI, но есть вероятность, что использование DI приведет к более чистому коду.

assylias
источник
3

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

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

Майкл Шоу
источник
2
 > Dependency Injection worth it **outside** of UnitTesting?
 > Are we justified in avoiding trying to program to an interface?

Многие ответы на этот вопрос можно обсудить как «вам это может понадобиться, потому что…», как YAGNI, если вы не хотите проводить юнит-тестирование.

Если вы спрашиваете, почему помимо юнит-тестов необходимо программировать интерфейс

Да , Dependency Injection стоит вне UnitTesting, если вам нужно инвертировать управление . Например, если одной реализации модуля нужен другой модуль, который недоступен на его уровне.

Пример: если у вас есть модули gui=> businesslayer=>driver => common.

В этом сценарии businesslayerможно использовать, driverноdriver не может использовать businesslayer.

Если driverтребуется некоторая функциональность более высокого уровня, вы можете реализовать эту функциональность, в businesslayerкоторой реализует интерфейс на commonуровне. driverнужно только знать интерфейс в commonслое.

k3b
источник
1

Да, вы - во-первых, забудьте о модульном тестировании как о причине разработки кода с использованием инструментов модульного тестирования, никогда не стоит менять дизайн кода так, чтобы он соответствовал искусственному ограничению. Если ваши инструменты заставляют вас делать это, приобретите более совершенные инструменты (например, Microsoft Fakes / Moles, которые предоставляют вам гораздо больше возможностей для создания фиктивных объектов).

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

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

gbjbaanb
источник
3
Я не думаю, что преобладающая мудрость о том, что частные методы не следует тестировать, имеет какое-либо отношение к сложности их тестирования. Если в приватном методе была ошибка, но она не влияла на поведение объекта, то это ни на что не влияет. Если в приватном методе была ошибка, которая действительно повлияла на поведение объекта, то существует провальный или отсутствующий тест, по крайней мере, в одном из открытых методов объекта.
Майкл Шоу
Это зависит от того, проверяете ли вы поведение или состояние. Очевидно, что частные методы должны быть протестированы каким-то образом, но если вы классический TDD, вы протестируете их, протестировав класс «в целом», инструменты тестирования, которые используют большинство людей, сосредоточены на тестировании каждого метода. индивидуально ... и моя точка зрения заключается в том, что последние, по сути, не проводят надлежащее тестирование, поскольку упускают возможность провести полный тест. Поэтому я согласен с вами, пытаясь подчеркнуть, как TDD был подорван тем, как некоторые (большинство?) Люди проводят модульное тестирование сегодня.
gbjbaanb
1

Dependency Injection также делает его более ясным , что ваш объект ИМЕЕТ зависимости.

Когда кто-то собирается наивно использовать ваш объект (не глядя на конструктор), он обнаружит, что ему потребуется настроить службы, соединения и т. Д. Это было неочевидно, поскольку им не нужно было передавать их конструктору. , Передача зависимостей проясняет, что нужно.

Вы также можете скрыть тот факт, что ваш класс может нарушать SRP. Если вы передаете много зависимостей (более 3), ваш класс может делать слишком много и должен быть реорганизован. Но если вы создаете их в конструкторе, этот запах кода будет скрыт.

Schleis
источник
0

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

Позвольте мне предложить другую альтернативу для моего сценария специально

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

Seph
источник