Инъекция зависимости: Черепахи все время вниз? - PullRequest
16 голосов
/ 31 декабря 2010

Так что мне интересно, как работает модульное тестирование в отношении внешних зависимостей. Здесь и в других местах я познакомился с внедрением зависимостей и тем, как это позволяет нам тестировать блок (A) кода. Однако меня смущает вопрос о том, как тестировать другие модули (B и C), которые теперь обладают внешней зависимостью, чтобы они могли внедрить ее в исходный модуль (A).

Например, скажем, некоторый класс Foo использует внешнюю зависимость ...

class Foo
{
    private ExternalDependency ed;
    public int doSomethingWithExternalDependency() {...}
}

И класс Бар отключен Foo ...

class Bar
{
    public int doSomethingWithFoo
    {
        Foo f = new Foo();
        int x = f.doSomethingWithExternalDependency();
        // Do some more stuff ...
        return result;
    }
}

Теперь я знаю, что могу использовать внедрение зависимостей, чтобы я мог проверить Foo , но тогда как мне проверить Bar ? Думаю, я снова могу использовать внедрение зависимостей, но в какой-то момент какой-то блок должен фактически создать внешнюю зависимость; так как мне проверить это устройство?

Ответы [ 5 ]

16 голосов
/ 31 декабря 2010

В приведенных вами примерах не используется Dependency Injection. Вместо этого Bar должен использовать Constructor Injection , чтобы получить экземпляр Foo, но нет смысла вводить класс concrete . Вместо этого вы должны извлечь интерфейс из Foo (назовем его IFoo) и вставить его в Bar:

public class Bar
{
    private IFoo f;

    public Bar(IFoo f)
    {
        this.f = f;
    }

    public int doSomethingWithFoo
    {
        int x = this.f.doSomethingWithExternalDependency();
        // Do some more stuff ...
        return result;
    }
}

Это позволяет вам всегда отделять потребителей и зависимости .

Да, все равно будет место, где вы должны составить граф объектов всего приложения. Мы называем это место Композиционный корень . Это компонент инфраструктуры приложения , поэтому вам не нужно его тестировать.

В большинстве случаев вам следует рассмотреть возможность использования DI-контейнера для этой части, а затем применить шаблон Register Resolve Release .

3 голосов
/ 31 декабря 2010

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

3 голосов
/ 31 декабря 2010

Чтобы использовать Dependency Injection, ваши классы должны иметь свои инъекции (несколько способов сделать это - конструктор, свойство) и не создавать их сами, как вы примеры.

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

class Foo
{
    private IExternalDependency ed;
    public int doSomethingWithExternalDependency() {...}

    public Foo(IExternalDependency extdep)
    {
      ed = extdep;
    }
}

Большинство людей используют mocking framework для проверки зависимостей при тестировании.

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

Это позволяет вам тестировать класс, не полагаясь на поведение его (реализованных) зависимостей.

В некоторых случаях вы можете использовать подделки или заглушки вместо насмешливого фреймворка. См. эту статью Мартина Фаулера о различиях.


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

1 голос
/ 31 декабря 2010

Когда вы тестируете класс модулем, вы должны смоделировать его зависимости, чтобы тестировать свой класс изолированно - это не зависит от внедрения зависимости.

Ответ на ваш вопрос о Bar: давпрыснуть Foo.Как только вы пойдете по пути DI, вы будете использовать его по всему стеку.Если вам действительно нужен новый Foo для каждого вызова doSomethingWithFoo, вы можете добавить FooFactory (который затем можно смоделировать для целей тестирования), если вы хотите, чтобы один Bar использовал много Foos.

0 голосов
/ 03 января 2011

Я хотел бы подчеркнуть, что в случае тестирования unit у вас должно быть два отдельных набора тестов: один для Foo.doSomethingWithExternalDependency и другой для Bar.doSomethingWithFoo.В последнем наборе создайте фиктивную реализацию Foo, и вы протестируете просто doSomethingWithFoo, предполагая, что doSomethingWithExternalDependency работает правильно.Вы тестируете doSomethingWithExternalDependency в отдельном наборе тестов.

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