Внедрение зависимостей и реализация конкретных зависимостей - PullRequest
3 голосов
/ 26 апреля 2011

Сначала позвольте мне представить реализацию без внедрения зависимостей (что нарушит принцип инверсии зависимостей):

public class MyValidator
{
  private readonly IChecksumGenerator _checksumGenerator;

  public MyValidator()
  {
     _checksumGenerator = new MyChecksumGenerator();
  }

  ...
}

Чтобы сделать этот код тестируемым, добавьте IChecksumGenerator:

public class MyValidator
{
  private readonly IChecksumGenerator _checksumGenerator;

  public MyValidator(IChecksumGenerator checksumGenerator)
  {
    _checksumGenerator = checksumGenerator; 
  }

  ...
}

Теперь мы можем легко протестировать MyValidator и заглушку контрольной суммы Generator, если требуется. Но реализация MyValidator алгоритмически связана с конкретной реализацией IChecksumGenerator (она просто не будет работать с любой другой реализацией). Таким образом, появляются некоторые проблемы:

  1. Мы представляем возможность того, что будет введен неверный IChecksumGenerator (например, из-за ошибки конфигурации контейнера IoC)
  2. Мы нарушаем инкапсуляцию, поскольку частная реализация MyValidator (связывание с MyChecksumGenerator) выходит за пределы класса

Лучшее решение, к которому я пришел, это следующее:

public class MyValidator
{
  private readonly IChecksumGenerator _checksumGenerator;

  public MyValidator()
  {
    _checksumGenerator = new MyChecksumGenerator;
  }

  internal MyValidator(IChecksumValidator checksumValidator)
  {
    _checksumValidator = checksumValidator;
  }

  ...
}

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

Как бы вы решили эту проблему?

Ответы [ 5 ]

5 голосов
/ 26 апреля 2011

Рефакторинг в Constructor Injection - очень хорошая идея, но я нахожу ограничения, выдвинутые в вопросе, странными. Я бы порекомендовал вам пересмотреть дизайн.

Если MyValidator работает только с одной конкретной реализацией IChecksumGenerator, это нарушит принцип подстановки Лискова (LSP). По сути, это также означает, что вы не сможете ввести Test Double , так как заглушка / макет / фальшивка / все, что не будет экземпляром «правильного» IChecksumGenerator.

В некотором смысле вы могли бы сказать, что API лжет о своих требованиях, потому что он утверждает , что он может работать с любым IChecksumGenerator, в то время как в действительности он работает только с одним конкретным типом - давайте назовем его OneAndOnlyChecksumGenerator. Хотя я бы порекомендовал изменить дизайн приложения, чтобы он соответствовал LSP, вы также можете изменить сигнатуру конструктора, чтобы быть честными относительно требования:

public class MyValidator
{
    private readonly OneAndOnlyChecksumGenerator checksumGenerator;

    public MyValidator(OneAndOnlyChecksumGenerator checksumGenerator)
    {
        this.checksumGenerator = checksumGenerator; 
    }

    // ...
}

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

3 голосов
/ 26 апреля 2011

Это не нарушение инкапсуляции. Проверка часто включает в себя контрольную сумму.

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

Надеюсь, это поможет.

2 голосов
/ 26 апреля 2011

В книге Внедрение зависимостей в .Net Марк Симанн называет это последнее решение Bastard Injection и считает его анти-паттерном. Это главным образом потому, что у вас все еще есть зависимость от конкретной реализации. Вы должны проверить эту книгу для более подробной информации.

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

Представьте, что вы хотите перейти на другую реализацию для IChecksumGenerator. Предполагая, что то, что я сказал выше, верно, вам нужно изменить это в 2 местах с помощью Bastard Injection; Создатель и Валидатор. Если контролировать DI Container, это будет означать, что он находится только в 1 месте - конфигурации контейнера.

Другим преимуществом предоставления контейнера DI контроля является то, что он снижает вероятность того, что будущие изменения будут более тесно связывать MyValidator с конкретным IChecksumValidator, вводя нарушения LSP.

1 голос
/ 26 апреля 2011

Я не вижу, как MyValidator может быть алгоритмически связан с IChecksumGenerator. IChecksumGenerator будет предоставлять MyValidator контракт, согласно которому при заданном наборе входных данных будет возвращаться определенный набор выходных данных.

То, как реализация IChecksumGenerator рассчитывает эти выходные данные, не имеет значения для MyValidator . Вот почему вы можете предоставить тестовую заглушку. Тестовая заглушка имеет жестко привязанное отображение между входами и выходами, чтобы вы могли проводить тестирование Реальная реализация будет использовать алгоритм.

Алгоритм может иметь много разных реализаций. Может быть реализация, которая оптимизирует использование памяти, а другая - скорость.

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

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

0 голосов
/ 26 апреля 2011

вам нужно отделить свой тестовый код от продукта 1.

в коде продукта, который вы можете использовать:

 var validator = new MyValidator(new MyChecksumGenerator());

в тестовом коде:

var validator = new MyValidator(new MyChecksumGeneratorStub());

гдеMyChecksumGeneratorStub реализует IChecksumGenerator.

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