Подтвердить что ты не робот

Инъекция зависимости: Черепахи полностью вниз?

Итак, мне интересно, как работает модульное тестирование в отношении обработки внешних зависимостей. Здесь и в других местах я знаком с инъекцией зависимостей и как это позволяет нам тестировать блок (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, но тогда как я могу проверить Бар? Я думаю, я могу, опять же, использовать инъекцию зависимостей, но в какой-то момент какой-то блок должен фактически создать внешнюю зависимость; так как я могу проверить это устройство?

4b9b3361

Ответ 1

Приведенные примеры не используют инъекцию зависимостей. Вместо этого Bar должен использовать Injection конструктора, чтобы получить экземпляр Foo, но нет смысла вводить класс конкретный. Вместо этого вы должны извлечь интерфейс из 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;
    }
}

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

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

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

Ответ 2

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

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

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

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

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

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

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

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


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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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