【问题标题】:Understanding why we use Inversion of control containers for unit testing了解为什么我们使用控制容器反转进行单元测试
【发布时间】:2014-11-23 22:09:03
【问题描述】:

我目前正在考虑将 Ninject 合并到我的单元测试中。在浏览与早期问题相关的一些非常聪明的帖子时(What is Ninject and when do you use it?http://martinfowler.com/articles/injection.html);我想我得到了一些核心概念,但我正在努力解决一些如果它的应用程序。

大多数时候,当 IOC 容器被提出时,它与将它们合并到您的单元测试中有关。但是,从实际的角度来看,如果我还没有将 Ninject 之类的框架整合到我的实际代码库中,那么在我的单元测试中使用它是否有意义?

以下面的示例为例(摘自 James Bender 的 Professional Test Driven Development with C#: Developing Real World Applications with TDD)

[TestFixture]
public class PersonServiceTests
{
    [Test]
    public void ShouldBeAbleToCallPersonServiceAndGetPerson()
    {
        var expected = new Person {Id = 1, FirstName = “John”, LastName = “Doe”};
        var kernel = new StandardKernel(new CoreModule());
        var personService = kernel.Get < PersonService > ();
        var actual = personService.GetPerson(expected.Id);
        Assert.AreEqual(expected.Id, actual.Id);
        Assert.AreEqual(expected.FirstName, actual.FirstName);
        Assert.AreEqual(expected.LastName, actual.LastName);
    }
}

使用 Ninject 编写我的测试如何改善我的生活(或任何继承我的代码的可怜的开发人员),而不是将我的测试编写为:

[TestFixture]
public class PersonServiceTests
{
    [Test]
    public void ShouldBeAbleToCallPersonServiceAndGetPerson()
    {
        var expected = new Person {Id = 1, FirstName = “John”, LastName = “Doe”};
        var personService = new PersonService();
        var actual = personService.GetPerson(expected.Id);
        Assert.AreEqual(expected.Id, actual.Id);
        Assert.AreEqual(expected.FirstName, actual.FirstName);
        Assert.AreEqual(expected.LastName, actual.LastName);
    }
}

当我将依赖倒置原则合并到我的代码中时;我认为 IOC 容器的价值是一种减少与我的类如何实例化相关的冗余/集中代码的方法(由于尊重依赖倒置原则)。但是,如果我不想在我的代码中使用 IOC 容器,那么仅将 Ninject 之类的东西用作我的单元测试框架堆栈的一部分是否可行?

【问题讨论】:

    标签: c# dependency-injection ninject inversion-of-control


    【解决方案1】:

    自动模拟容器可以解决这个确切的问题。它将使用您选择的模拟框架自动存根/设置传入的依赖项,从而维护更少的代码并更多地关注测试。

    对于以下依赖:

    public class SystemUnderTest
    {
        public SystemUnderTest(IMockA mockA, IMockB mockB, IMockC mockC, IMockD mockD)
        {
        }
    
        public int Exercise()
        {
        }
    }
    

    而不是这样做:

    [Fact]
    public void Test()
    {
        var sut = new SystemUnderTest(
            Mock.Of<MockA>(Mock.Of<UnknownA>(Mock.Of<UnknownB>(), Mock.Of<UnknownC>())),
            Mock.Of<MockB>(Mock.Of<UnknownC>()),
            Mock.Of<MockC>(Mock.Of<UnknownD>(), Mock.Of<UnknownE>()),
            Mock.Of<MockD>(Mock.Of<UnknownD>())
        );
    }
    

    它可以缩短为:

    [Fact]
    public void Test()
    {
        var sut = Container.Resolve<SystemUnderTest>();
        var mockA = Container.Resolve<Mock<MockA>>().Setup(...);
        var mockB = Container.Resolve<Mock<MockB>>().Setup(...);
    }
    

    完全披露:我制作了一个简单的自动模拟容器库Tethos,这些列出的示例来自该库。

    它支持所有流行的模拟框架MoqNSubstituteFakeItEasy

    【讨论】:

      【解决方案2】:

      为什么要在测试中使用 DI 框架?你的对象图很难组合吗?

      我个人认为你最好让测试显式创建它们的依赖项,因为然后注入模拟或存根将在测试中显式并且非常可见。

      它还隐藏了有关创建复杂依赖层次结构的任何问题。在测试中创建所有对象意味着您将不得不感受到创建这些依赖项的痛苦,并且可能会激励您改进设计。

      我认为在这种情况下使用 DI 框架不会给你带来任何好处,除了混淆你的测试所具有的依赖关系。

      集成测试可能是另一回事,但对于单元测试...

      【讨论】:

      • 对于集成测试,您可能需要使用容器,但对于单元测试则不需要。 +1
      • @Steven 是的。对于我的集成测试,我一直在使用great container,它鼓励您陷入成功的深渊。不能高度推荐它;-)
      • 这似乎是一个不错的容器。我绝对应该有一天尝试一下 :-)
      猜你喜欢
      • 2011-07-26
      • 1970-01-01
      • 1970-01-01
      • 2017-04-17
      • 1970-01-01
      • 2014-08-07
      • 2017-04-26
      • 2020-11-07
      • 2015-07-12
      相关资源
      最近更新 更多