Действительно ли тестируемость является одной выравнивание для внедрения зависимости?

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

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

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

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

Это также делает код тяжелее, чтобы читать, инстанцировать репозитория, который я теперь должен записать

new OrdersRepository(new MyLinqDataContext())

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

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

14
задан fearofawhackplanet 6 June 2010 в 20:17
поделиться

8 ответов

Основное преимущество внедрения зависимостей - это тестирование. И вы натолкнулись на кое-что, что мне показалось странным, когда я впервые начал применять разработку через тестирование и 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" (или муравье) для графов объектов ...контейнер строит за вас граф зависимостей и выполняет всю тяжелую работу по построению). При использовании контейнера IOC вы возвращаете скрытие зависимости, которое вы упомянули в своем OP. Зависимости настраиваются и обрабатываются контейнером как отдельная задача, а вызывающий код может просто запросить экземпляр интерфейса.

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

12
ответ дан 1 December 2019 в 12:12
поделиться

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

1
ответ дан 1 December 2019 в 12:12
поделиться

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

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

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

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

2
ответ дан 1 December 2019 в 12:12
поделиться

Прелесть внедрения зависимостей заключается в возможности изолировать ваши компоненты .

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

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

2
ответ дан 1 December 2019 в 12:12
поделиться

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

3
ответ дан 1 December 2019 в 12:12
поделиться

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

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

1
ответ дан 1 December 2019 в 12:12
поделиться

На 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 имеет универсальные шаблоны, так что это должно быть возможно.

0
ответ дан 1 December 2019 в 12:12
поделиться

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

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

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

  1. OrdersRepository
  2. OrderDataAccessor

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

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

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

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

var repository = container.Resolve<OrdersRepository>;
1
ответ дан 1 December 2019 в 12:12
поделиться
Другие вопросы по тегам:

Похожие вопросы: