2012-03-25 56 views
2

我想了解控制反转以及它如何帮助我进行单元测试。我已经阅读了几篇关于国际奥委会的在线解释以及它做了什么,但我只是不完全理解它。控制反转如何帮助我?

我开发了一个示例项目,其中包括使用StructureMap进行单元测试。 StructureMap设置如下代码:

private readonly IAccountRepository _accountRepository 

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

我不理解,虽然事情,是因为我看到它,我可以简单地宣布以上,如下:

AccountRepository _accountRepository = new AccountRepository(); 

它将执行与之前的代码一样。所以,我只是想知道是否有人能够以简单的方式向我解释使用IOC的好处(特别是在处理单元测试时)。

感谢

+1

这不是IoC。 – 2012-03-25 06:47:35

回答

2

Inversion of Control是让框架回调到用户代码的概念。这是一个非常抽象的概念,它只是描述了一个库和一个框架之间的区别,或者它可以被描述为“一个框架的定义特征”。我们的代码调用了一个库,而一个框架控制着我们的代码。任何框架提供了钩子,允许我们插入我们的代码。

Inversion of Control是一种模式,只有在构建框架时您是框架开发人员,或者您是应用程序开发人员与框架代码交互时才可以应用。但是,IoC在专门处理应用程序代码时不适用。

取决于抽象而不是实现的行为称为依赖性反转,而依赖性反转可以由应用程序和框架开发人员实践。所以你所说的IoC实际上是依赖性倒置,正如Krzysztof所说的那样:你所做的不是IoC。我从现在开始讨论依赖倒置。

基本上有两种依赖倒置的形式/实现:服务定位器和依赖注入。

使用服务定位器模式,您需要依赖关系在类中调用静态工厂。在一般情况下,它看起来像这样:

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

这个例子应该熟悉你,因为这你做你的Logon方法是什么:您正在使用Service Locator模式。

随着依赖注入模式,你注入一个类需要从外部的所有依赖项;最好使用构造函数。类本身不负责获得它的依赖关系。这个责任被调高了。上述类是这样当使用依赖注入:

public class Service 
{ 
    private readonly IDependency dependency; 

    public Service(IDependency dependency) 

{ this.dependency =依赖; }

public void SomeOperation() 

{ this.dependency.Execute(); }}

两种模式是依赖倒置,因为在这两种情况下,Service类是不负责创建的依赖关系,不知道它正在使用的实现。它只是与界面进行交谈。这两种模式都为您提供了一个类正在使用的实现的灵活性,从而允许您编写更灵活的软件。

然而,服务定位器模式存在很多问题,这就是为什么它被认为是反模式。您已经遇到了这些问题,因为您想知道依赖反转(您的案例中的服务定位器)如何帮助您进行单元测试。

答案是Service Locator模式对单元测试没有帮助。相反:它使得单元测试非常困难。通过让该类调用ObjectFactory,可以在两者之间创建一个硬依赖关系。替换IAccountRepository进行测试,也意味着您的单元测试必须使用ObjectFactory。这使得你的单元测试更难阅读。但更重要的是,由于ObjectFactory是一个静态实例,所有的单元测试都使用同一个实例,这使得难以在每个测试的基础上独立运行测试和交换实现。

我过去使用过服务定位符模式,并且我处理这个的方式是通过在我的服务定位器中注册依赖关系,我可以逐线程地改变依赖关系(使用[ThreadStatic]字段封面)。这使我可以并行运行我的测试(默认情况下是MSTest),同时保持测试隔离。然而,这个问题是这个问题变得非常复杂,使用各种技术手段使测试变得困难,这让我花了很多时间解决这些技术问题,而我本可以编写更多的测试。

这些问题的真正解决方案是依赖注入。一旦你通过构造函数注入了一个类需要的依赖关系,所有这些问题就会消失。这不仅明确了类需要什么依赖关系(没有隐藏的依赖关系),而且每个单元测试本身都负责注入它所需的依赖关系。这使得编写测试变得更加容易,并且可以防止您在单元测试中配置DI容器。

延伸阅读:Service Locator is an Anti-Pattern

+0

我同意你的观点,即通过构造函数传递依赖关系更容易,因为你不需要一个框架来解决你的依赖关系。然而,我不同意你的“静态实例”的论点。没有必要把它作为一个Singelton来实现。它基本上只是一个返回某个数据访问对象实例的静态方法。这并不会导致在尝试并行运行测试时出现问题,因为工厂没有共享状态。 – TGH 2012-03-25 20:35:18

+2

控制反转的定义是容器选择组件,而不是选择组件的组件。 IoC的重点是创建松散耦合且更容易测试的组件。使用Service Locator模式,组件类正在选择/使用容器,但容器正在选择要返回的IDependency。所以服务定位器模式只有一半。你交易一个硬依赖另一个。 – 2012-03-27 14:27:37

1

这背后的想法是让你换出的默认帐户存储库实现了更单元测试版本。在您的单元测试中,您现在可以实例化一个不会进行数据库调用的版本,而是返回固定数据。通过这种方式,您可以专注于测试方法中的逻辑并释放对数据库的依赖关系。

这是在许多层面上更好地: 1)你的测试更稳定,因为你再也不用担心测试数据库 2)你的测试运行,由于未能数据的变化更快,因为你不叫3)你可以更容易地模拟所有的测试条件,因为你的模拟版本库可以返回测试任何条件所需的任何类型的数据。

0

回答你的问题的关键是可测试性,如果你想管理注入对象的生命周期,或者如果你打算让IoC容器为你做。

比方说,您正在编写一个使用您的存储库并希望对其进行测试的类。

如果你这样做了以下内容:

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

     return _accountRepository.GetEntityFromDatabaseBy(id); 
    } 
} 

当您尝试测试这种方法,你会发现有很多的并发症: 1.必须有已经建立了一个数据库。 2.您的数据库需要具有您要查找的实体的表。 3.您用于测试的ID必须存在,如果您因任何原因将其删除,则现在您的自动测试已被破坏。

相反,如果您有类似以下内容:

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实体,而无需使用数据库。

有一百万件事情你仍然可以在这里改进这个例子。你可以使用像Rhino Mocks或Moq这样的Mocking框架来创建伪造的对象,而不是像我在示例中那样自己编写它们。

通过这样做,MyClass类完全独立于AccountRepository,所以这就是当Loosley耦合概念发挥作用并且您的应用程序可测试且更易维护时。

有了这个例子,你可以看到IoC本身的好处。现在,如果您不使用IoC容器,则必须实例化所有依赖项并在Composition Root中正确注入它们或配置IoC容器,以便它可以为您执行此操作。

问候。