Как инверсия контроля помогает мне? - PullRequest
2 голосов
/ 25 марта 2012

Я пытаюсь понять, что такое Inversion of Control и как он помогает мне в модульном тестировании. Я прочитал несколько онлайн-объяснений МОК и что он делает, но я просто не совсем понимаю.

Я разработал пример проекта, который включал использование StructureMap для модульного тестирования. Код установки StructureMap выглядит следующим образом:

private readonly IAccountRepository _accountRepository

public Logon()
{
    _accountRepository = ObjectFactory.GetInstance<IAccountRepository>();
}

Хотя я не понимаю, как я понимаю, я мог бы просто заявить следующее:

AccountRepository _accountRepository = new AccountRepository();

И это сделало бы то же самое, что и предыдущий код. Поэтому мне просто интересно, может ли кто-нибудь помочь мне объяснить простым способом, в чем заключается преимущество использования МОК (особенно при работе с модульным тестированием).

Спасибо

Ответы [ 3 ]

2 голосов
/ 25 марта 2012

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

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

Акт зависимости от абстракций вместо реализаций называется инверсией зависимости, а инверсия зависимости может практиковаться как разработчиками приложений, так и разработчиками инфраструктуры.Так что то, что вы называете IoC, на самом деле является инверсией зависимости, и, как уже заметил Кшиштоф: то, что вы делаете, это не IoC.Теперь я буду обсуждать инверсию зависимости.

Существует две основные формы / реализации инверсии зависимости: сервисный локатор и внедрение зависимостей.

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

public class Service
{
    public void SomeOperation() {
        IDependency dependency = 
            ServiceLocator.GetInstance<IDependency>();
        dependency.Execute();
    }
}

Этот пример должен показаться вам знакомым, поскольку то, что вы делаете в своем методе Logon: вы используете шаблон Service Locator.

Используя шаблон Dependency Injection , вы вводите все зависимости, которые нужны классу извне;желательно с помощью конструктора.Сам класс не несет ответственности за получение своих зависимостей.Эта ответственность перемещается вверх по стеку вызовов.Предыдущий класс выглядел бы так при использовании Dependency Injection:

public class Service
{
    private readonly IDependency dependency;

    public Service(IDependency dependency)

{this.dependency = зависимость;}

    public void SomeOperation()

{this.dependency.Execute ();}}

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

Однако существует много проблем с шаблоном Service Locator, и поэтому он считается анти-шаблоном.,Вы уже сталкиваетесь с этими проблемами, так как вам интересно, как Dependency Inversion (Service Locator в вашем случае) помогает вам с модульным тестированием.

Ответ заключается в том, что шаблон Service Locator не помогает с модульным тестированием.Напротив: это делает юнит-тестирование очень сложным.Позволяя классу вызывать ObjectFactory, вы создаете жесткую зависимость между ними.Замена IAccountRepository для тестирования также означает, что в вашем модульном тесте должен использоваться ObjectFactory.Это затрудняет чтение ваших юнит-тестов.Но что более важно, поскольку ObjectFactory является статическим экземпляром, все модульные тесты используют этот же экземпляр, что затрудняет выполнение тестов в изоляции и замену реализаций для каждого теста.

IВ прошлом я использовал шаблон Service Locator, и способ, которым я справлялся с этим, заключался в регистрации зависимостей в моем Service Locator, которые я мог изменять в потоке за ниткой (используя поле [ThreadStatic] под обложками).Это позволило мне выполнять свои тесты параллельно (что MSTest делает по умолчанию), сохраняя при этом изолированность тестов.Однако проблема в том, что это усложняется очень быстро, запутывает тесты всеми видами технических вещей, и это заставило меня потратить много времени на решение этих технических проблем, в то время как я мог бы писать больше тестов.

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

Дополнительная информация: Сервисный локатор - это анти-шаблон .

1 голос
/ 25 марта 2012

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

Это лучше на многих уровнях: 1) Ваши тесты более стабильны, так как вам больше не нужно беспокоиться о сбоях тестов из-за изменений данных в базе данных. 2) Ваши тесты будут выполняться быстрее, поскольку вы не вызываете внешний источник данных 3) Вы можете с легкостью смоделировать все ваши условия тестирования, так как ваш смоделированный репозиторий может вернуть любой тип данных, необходимый для проверки любого условия

0 голосов
/ 03 апреля 2012

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

Скажем, например, что выпишите класс, который использует ваш репозиторий, и вы хотите его протестировать.

Если вы сделаете что-то вроде следующего:

public class MyClass
{
   public MyEntity GetEntityBy(long id)
   {
      AccountRepository _accountRepository = new AccountRepository();

      return _accountRepository.GetEntityFromDatabaseBy(id);
   }
}

Когда вы попытаетесь протестировать этот метод, вы обнаружите, что тамЕсть много сложностей: 1. База данных уже должна быть настроена.2. В вашей базе данных должна быть таблица, в которой есть искомая сущность.3. Идентификатор, который вы используете для своего теста, должен существовать, если вы удалите его по какой-либо причине, ваш автоматизированный тест теперь не работает.

Если вместо этого у вас есть что-то вроде следующего:

public interface IAccountRepository
{
   AccountEntity GetAccountFromDatabase(long id);
}

public class AccountRepository : IAccountRepository
{
   public AccountEntity GetAccountFromDatabase(long id)
   {
      //... some DB implementation here
   }
}

public class MyClass
{
   private readonly IAccountRepository _accountRepository;

   public MyClass(IAccountRepository accountRepository)
   {
      _accountRepository = accountRepository;
   }

   public AccountEntity GetAccountEntityBy(long id)
   {
      return _accountRepository.GetAccountFromDatabase(id)
   }
}

Теперь, когда у вас есть возможность тестировать класс MyClass изолированно без необходимости создания базы данных.

Чем это выгодно?Например, вы можете сделать что-то вроде этого (при условии, что вы используете Visual Studio, но те же принципы применимы и к NUnit, например):

[TestClass]
public class MyClassTests
{
   [TestMethod]
   public void ShouldCallAccountRepositoryToGetAccount()
   {
      FakeRepository fakeRepository = new FakeRepository();

      MyClass myClass = new MyClass(fakeRepository);

      long anyId = 1234;

      Account account = myClass.GetAccountEntityBy(anyId);

      Assert.IsTrue(fakeRepository.GetAccountFromDatabaseWasCalled);
      Assert.IsNotNull(account);
   }
}

public class FakeRepository : IAccountRepository
{
   public bool GetAccountFromDatabaseWasCalled { get; private set; }

   public Account GetAccountFromDatabase(long id)
   {
      GetAccountFromDatabaseWasCalled = true;

      return new Account();
   }
}

Итак, как вы можете видеть, вы можете очень увереннопроверьте, что класс MyClass использует экземпляр IAccountRepository для получения сущности Account из базы данных без необходимости иметь базу данных на месте.

Есть еще миллион вещей, которые вы можете сделать здесь, чтобы улучшить пример.Вы можете использовать среду Mocking, такую ​​как Rhino Mocks или Moq, для создания ваших поддельных объектов, вместо того, чтобы кодировать их самостоятельно, как я делал в примере.

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

В этом примере вы можете увидеть преимущества IoC сами по себе.Теперь, если вы НЕ ИСПОЛЬЗУЕТЕ контейнер IoC, вам необходимо создать все зависимости и внедрить их соответствующим образом в Composition Root или настроить контейнер IoC, чтобы он мог это сделать за вас.

Привет.

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