Является ли только одно испытание обоснованием для инъекций зависимостей?

Преимущества DI, насколько мне известно, следующие:

  • Уменьшенные зависимости
  • Код многократного использования
  • Более проверяемый код
  • Более читаемый код

Скажем, у меня есть репозиторий OrderRepository, который действует как репозиторий для объекта Order, сгенерированного с помощью Linq to Sql dbml. Я не могу сделать свой общий репозиторий заказов, поскольку он выполняет сопоставление между объектом Linq Order и моим собственным классом домена POCO заказа.

Так как OrderRepository по необходимости зависит от конкретного Linq to Sql DataContext, передача параметров DataContext нельзя сказать, чтобы сделать код повторно используемым или уменьшить зависимости любым значимым способом.

Это также делает код более трудным для чтения, поскольку для создания экземпляра репозитория мне теперь нужно написать

new OrdersRepository(new MyLinqDataContext())

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

В общем, я думаю, что это будет довольно ужасный дизайн, но это даст возможность облегчить модульное тестирование. Достаточно ли этого оправдания? Или есть третий способ? Мне было бы очень интересно услышать мнения.

Ответы

Ответ 1

Основным преимуществом впрыскивания является тестирование. И вы попали на что-то, что показалось мне странным, когда я впервые начал использовать Test Driven Development и DI. DI действительно разрушает инкапсуляцию. Единичные тесты должны проверять решения, связанные с внедрением; как таковой, вы в конечном итоге разоблачаете детали, которые вы бы не использовали в чисто инкапсулированном сценарии. Ваш пример - хороший, где, если вы не выполняете разработку, основанную на тестах, вы, вероятно, захотите инкапсулировать контекст данных.

Но где вы говорите: поскольку OrderRepository по необходимости зависит от конкретного Linq to Sql DataContext, я бы не согласился - у нас такая же настройка и зависит только от интерфейса. Вы должны разорвать эту зависимость.

В качестве примера сделаем еще один шаг, как вы проверите свой репозиторий (или его клиентов) без использования базы данных? Это один из основных принципов модульного тестирования - вы должны иметь возможность тестировать функциональность без, взаимодействуя с внешними системами. И нигде это не имеет значения, чем в базе данных. Dependency Injection - это шаблон, который позволяет разрывать зависимости от подсистем и слоев. Без этого модульные тесты в конечном итоге требуют обширной настройки прибора, становятся трудно писать, хрупки и слишком прокляты медленно. В результате вы просто не напишете их.

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

В модульных тестах:

// From your example...

new OrdersRepository(new InMemoryDataContext());

// or...

IOrdersRepository repo = new InMemoryDataContext().OrdersRepository;

и In Production (с использованием контейнера IOC):

// usually...

Container.Create<IDataContext>().OrdersRepository

// but can be...

Container.Create<IOrdersRepository>();

(Если вы не использовали контейнер IOC, это клей, который делает работу DI. Подумайте об этом как "make" (или ant) для графиков объектов... контейнер создает граф зависимостей для вы и делаете весь тяжелый подъем для строительства). При использовании контейнера IOC вы возвращаете скрытую зависимость, которую вы указываете в своем OP. Зависимости настраиваются и обрабатываются контейнером в качестве отдельной проблемы - и вызывающий код может просто запрашивать экземпляр интерфейса.

Там действительно отличная книга, в которой подробно рассматриваются эти проблемы. Проверьте xUnit Test Patterns: Рефакторинг тестового кода, by Mezaros. Это одна из тех книг, в которой ваши возможности разработки программного обеспечения выходят на следующий уровень.

Ответ 3

Небольшой комментарий во-первых: инверсия зависимостей = инверсия зависимостей IoC+. Самое важное для тестирования и того, что вы на самом деле описываете, - это инверсия зависимостей.

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

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

Если у вас есть контейнер DI, это происходит автоматически; контейнер DI действует как factory и соединяет объект вместе.

Ответ 4

Красота Injection Dependency - это способность изолировать ваши компоненты.

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

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

Ответ 5

Сила инъекции зависимостей возникает, если вы используете контейнер инверсии управления, такой как StructureMap. При использовании этого вы нигде не увидите "нового" - контейнер будет контролировать вашу конструкцию объекта. Таким образом, все не осознает зависимости.

Ответ 6

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

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

Ответ 7

Конструкция тестов здесь всегда зависит от SUT (System Under Test). Задайте себе вопрос - что я хочу проверить?

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

Если ваш репозиторий выполняет какое-либо сопоставление или бизнес-логику и действует как accessor к базе данных, тогда это происходит, когда необходимо сделать разложение, чтобы ваша система соответствовала SRP ( Единый принцип ответственности). После разложения у вас будет 2 объекта:

  • OrdersRepository
  • OrderDataAccessor

Протестируйте их отдельно друг от друга, разбив зависимости с DI.

Как из уродства конструктора... Используйте конструкцию DI для создания ваших объектов. Например, используя Unity ваш конструктор:

var repository = new OrdersRepository(new MyLinqDataContext());

будет выглядеть следующим образом:

var repository = container.Resolve<OrdersRepository>;

Ответ 8

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

Если я отвечу "Да", я думаю о тестировании систем, когда у вас нет ничего, что вы можете легко проверить напрямую. В физическом мире необычно включать порты, туннели доступа, выступы и т.д., Чтобы обеспечить простой и прямой способ проверки состояния систем, машин и т.д. Это кажется разумным в большинстве случаев. Например, нефтепровод обеспечивает инспекционные люки, позволяющие вводить оборудование в систему для целей испытаний и ремонта. Они созданы специально и не обеспечивают никакой другой функции. Реальный вопрос, однако, заключается в том, что эта парадигма подходит для разработки программного обеспечения. Часть меня хотела бы сказать "да", но ответ, кажется, принесет себестоимость и оставляет нас в этой прекрасной серой области балансирующих выгод и затрат.

Аргумент "нет" действительно сводится к причинам и целям разработки программных систем. DI - прекрасный образец для продвижения свободной связи кода, то, чему нас учат в наших классах ООП, является очень важной и мощной концепцией дизайна для улучшения ремонтопригодности кода. Проблема в том, что, как и все инструменты, это может быть неправильно использовано. Я не соглашусь с Робом ответить выше частично, потому что преимущества DI не в первую очередь тестируются, а в содействии слабосвязанной архитектуре. И я бы утверждал, что при использовании систем проектирования, основанных исключительно на способности их тестировать, в таких случаях предполагается, что либо архитектура имеет недостатки, либо тестовые примеры неправильно настроены, и, возможно, и то, и другое.

Хорошо продуманная системная архитектура в большинстве случаев по своей сути прост в тестировании, а введение насмешливых фреймворков за последнее десятилетие делает тестирование намного проще. Опыт научил меня, что любая система, которую мне трудно испытать, имела какой-то аспект ее слишком тесно связанного. Иногда (реже) это оказалось необходимым, но в большинстве случаев это было не так, и обычно, когда казалось бы, простая система казалась слишком трудной для тестирования, это объяснялось тем, что парадигма тестирования была ошибочной. Я видел, что DI используется как средство для обхода дизайна системы, чтобы позволить тестировать систему, и риски, безусловно, перевешивают намеченные награды, а системная архитектура эффективно повреждена. Под этим я подразумеваю задние двери в код, приводящие к проблемам безопасности, код, раздутый тестом-специфическим поведением, которое никогда не используется во время выполнения, и спагеттификацию исходного кода, так что вам понадобилось пару Sherpa и совета Ouija, чтобы выяснить, какие путь был вверх! Все это в поставляемом коде производства. Результирующие затраты с точки зрения обслуживания, кривой обучения и т.д. Могут быть астрономическими, а для небольших компаний такие потери могут оказаться разрушительными в долгосрочной перспективе.

ИМХО, я не верю, что DI всегда должен использоваться как средство, чтобы просто улучшить тестируемость кода. Если DI является вашим единственным вариантом для тестирования, тогда дизайн обычно необходимо реорганизовать. С другой стороны, внедрение DI по дизайну, где он может использоваться как часть кода времени выполнения, может обеспечить явные преимущества, но не следует злоупотреблять, поскольку это также может привести к злоупотреблению классами, и это не должно быть чрезмерным, используется просто потому, что кажется классным и легким, поскольку в таких случаях он может усложнить дизайн вашего кода.

: -)

Ответ 10

Возможно создание общих объектов доступа к данным в Java:

package persistence;

import java.io.Serializable;
import java.util.List;

public interface GenericDao<T, K extends Serializable>
{
    T find(K id);
    List<T> find();
    List<T> find(T example);
    List<T> find(String queryName, String [] paramNames, Object [] bindValues);

    K save(T instance);
    void update(T instance);
    void delete(T instance);
}

Я не могу говорить для LINQ, но у .NET есть дженерики, поэтому это должно быть возможно.