Оправдывает ли тестируемость оправдание внедрения зависимости? - PullRequest
14 голосов
/ 07 июня 2010

Насколько мне известно, преимущества DI:

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

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

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

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

new OrdersRepository(new MyLinqDataContext())

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

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

Ответы [ 10 ]

12 голосов
/ 07 июня 2010

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

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

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

Если взять ваш пример еще дальше, вы можете получить

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

// From your example...

new OrdersRepository(new InMemoryDataContext());

// or...

IOrdersRepository repo = new InMemoryDataContext().OrdersRepository;

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

// usually...

Container.Create<IDataContext>().OrdersRepository

// but can be...

Container.Create<IOrdersRepository>();

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

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

3 голосов
/ 08 июня 2010

Внедрение зависимостей - это просто средство для завершения Это способ включить слабую связь .

2 голосов
/ 07 июня 2010

Прелесть Dependency Injection заключается в способности изолировать ваши компоненты .

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

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

2 голосов
/ 07 июня 2010

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

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

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

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

1 голос
/ 28 июня 2011

Инъекция зависимости широко используется в наши дни. Эта статья объясняет все преимущества внедрения зависимости. http://venkataspinterview.blogspot.com/2011/06/interview-questions-related-to.html

1 голос
/ 30 сентября 2010

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

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

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

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

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

: -)

1 голос
/ 07 июня 2010

Дизайн тестов здесь всегда зависит от SUT (тестируемой системы). Задайте себе вопрос - что я хочу проверить?

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

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

  1. OrdersRepository
  2. OrderDataAccessor

Проверять их отдельно друг от друга, ломая зависимости с помощью DI.

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

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

будет выглядеть так:

var repository = container.Resolve<OrdersRepository>;
1 голос
/ 07 июня 2010

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

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

1 голос
/ 07 июня 2010

Внедрение зависимости зависит от использования контейнера Inversion of Control, такого как StructureMap. При использовании этого вы не увидите «нового» в любом месте - контейнер будет управлять строительством вашего объекта. Таким образом, все не знают о зависимостях.

0 голосов
/ 07 июня 2010

Можно написать общие объекты доступа к данным в 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 имеет обобщенные значения, поэтому это должно быть возможно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...