【问题标题】:What should be the strategy of unit testing when using IoC?使用 IoC 时单元测试的策略应该是什么?
【发布时间】:2011-01-19 08:07:42
【问题描述】:

在阅读了有关依赖注入和 IoC 的所有内容后,我决定尝试在我们的应用程序中使用 Windsor Container(它是一个 50K LOC 的多层 Web 应用程序,所以我希望这不是矫枉过正)。我使用了一个简单的静态类来包装容器,并在启动应用程序时对其进行了初始化,目前它工作得很好。

我的问题是关于单元测试的。我知道 DI 将使我在那里的生活变得更加轻松,因为我可以将类协作者的存根/模拟实现注入到被测类中。我已经使用这种技术编写了几个测试,这对我来说似乎很有意义。我不确定我是否应该在单元测试中也使用 IoC(在本例中为 Windsor Castle)(可能以某种方式将其配置为针对我的特殊情况返回存根 / 模拟)还是更好地连接所有依赖项在测试中手动。您认为什么以及哪些做法对您有用?

【问题讨论】:

标签: c# unit-testing dependency-injection inversion-of-control castle-windsor


【解决方案1】:

单元测试中不需要 DI 容器,因为依赖项是通过使用 Rhino MocksMoq 等框架生成的模拟对象提供的。因此,例如,当您测试一个依赖于某个接口的类时,这种依赖通常是通过构造函数注入提供的。

public class SomeClassToTest
{
    private readonly ISomeDependentObject _dep;
    public SomeClassToTest(ISomeDependentObject dep)
    {
        _dep = dep;
    }

    public int SomeMethodToTest()
    {
        return _dep.Method1() + _dep.Method2();
    }
}

在您的应用程序中,您将使用 DI 框架在构造函数中传递ISomeDependentObject 的一些实际实现,该构造函数本身可能依赖于其他对象,而在单元测试中您创建一个模拟对象,因为您只想测试这个类隔离中。 Rhino Mocks 示例:

[TestMethod]
public void SomeClassToTest_SomeMethodToTest()
{
    // arrange
    var depStub = MockRepository.CreateStub<ISomeDependentObject>();
    var sut = new SomeClassToTest(depStub);
    depStub.Stub(x => x.Method1()).Return(1);
    depStub.Stub(x => x.Method2()).Return(2);

    // act
    var actual = sut.SomeMethodToTest();

    // assert
    Assert.AreEqual(3, actual);
}

【讨论】:

  • 听起来很合理。毕竟,这正是我正在做的事情。我只是不确定这是否是一个好习惯,尤其是当有很多依赖项并且我必须手动模拟它们时。
  • 看看我在回答 Thomas 中谈到的自动模拟,它确实简化了具有大量依赖项的测试类。 :-)
【解决方案2】:

我正在开发一个包含大约 400 个单元测试的 ASP.NET MVC 项目。我使用 Ninject 进行依赖注入,使用 MBUnit 进行测试。

Ninject 对于单元测试来说并不是真正必要的,但它减少了我必须输入的代码量。我只需要指定一次(每个项目)我的接口应该如何实例化,而不是每次我初始化被测试的类时都这样做。

为了节省编写新测试的时间,我创建了测试夹具基类,其中包含尽可能多的通用设置代码。这些类中的设置过程初始化假存储库,为测试用户创建一些测试数据和假身份。单元测试仅初始化太具体而无法进入通用设置过程的数据。

我也在一些测试中模拟对象(而不是伪造),但我发现伪造数据存储库会导致更少的工作和更准确的测试。

例如,与我使用存储库假时相比,在使用存储库模拟时检查我所测试的方法是否正确地将所有更新提交到存储库后会更困难。

一开始设置工作量很大,但从长远来看确实帮助我减少了很多时间。

【讨论】:

    【解决方案3】:

    我刚刚编写了一个非常相似的样式和大小的应用程序。我不会在单元测试中放置任何依赖注入,因为它不够复杂,没有必要。你应该使用一个模拟框架来创建你的模拟(RhinoMocks / Moq)。

    Moq 中的 Automocking 或 Rhinomocks 中的 Auto Mock Container 也将进一步简化您的模拟。

    自动模拟允许您获取对象 您想要测试的类型 手动设置模拟。全部 依赖项被自动模拟 (假设它们是接口)和 注入类型构造函数。如果 你需要你可以设置预期 行为,但您不必这样做。

    【讨论】:

    • 是的,但 Auto Mock Container 不也是 IoC 容器吗? ;) 只是它在解决依赖关系方面更加轻松:它将生成服务的默认模拟实现,而不会像“正确的”Windsor 容器那样为未解决的东西抛出异常。
    • ...最重要的是,它只需要两行即可创建!您不需要注册任何依赖项这一事实意味着它比常规 IoC 更容易加载。使用自动模拟的另一个原因是抽象出对被测对象构造函数的调用。因此,如果签名更改,您不必更改大量测试,从而使您的测试类不那么脆弱。 :-)
    【解决方案4】:

    正如 Darin 已经指出的那样,如果您有模拟,则不需要使用 DI。 (但是,DI 也有其他一些好处,首先包括减少代码中的依赖性,从长远来看,这使您的代码更容易维护和扩展。)

    我个人更喜欢在单元测试中连接所有内容,因此尽可能少地依赖外部框架、配置文件等。

    【讨论】:

      猜你喜欢
      • 2010-11-07
      • 1970-01-01
      • 2010-09-13
      • 1970-01-01
      • 2010-09-28
      • 1970-01-01
      • 2011-02-04
      • 2019-06-30
      • 2011-09-02
      相关资源
      最近更新 更多