【问题标题】:Can I use an IoC container in unit/integration tests?我可以在单元/集成测试中使用 IoC 容器吗?
【发布时间】:2012-08-21 19:05:23
【问题描述】:

我有一个 IoC 容器在解析一些接口时做一些复杂的对象构造。我想在我的单元/集成测试中使用这些接口的实现。使用 IoC 容器在测试中解析这些接口有什么问题吗?还是应该在这种情况下手动构建实例?

【问题讨论】:

    标签: unit-testing integration-testing ioc-container


    【解决方案1】:

    当我们对一个类进行单元测试时,我们关心的是'这个类是否按照我们的意愿去做'。 我们的出发点是一个完全构造的实例;我们如何到达那里不是单元测试问题,尽管它可能被视为集成测试问题。

    假设我们有,

    A
      A(IB b, IC c)
    
    B : IB
      B(ID d)
    
    C : IC
    
    D : ID
    

    其中:
    IBB 的接口,
    ICC 的接口,
    IDD 的接口。

    当对A进行单元测试时,B使用ID这个事实应该没有实际意义(如果不是,那么我们应该看看我们的接口。让A访问IB.D.xxx()不好),所有我们需要做的是为A 提供IBIC 的一些实现(模拟/存根)。

    A 的单元测试而言,A 实例是手动创建的还是容器创建的并不重要。无论哪种方式,我们都会得到相同的对象。

    只要我们将模拟作为第一级依赖项传递,那么在使用容器而不是手动创建对象时,就没有保存。仅当我们使用 IOC 创建对象图时才会进行保存,但如果我们这样做,那么我们将进入集成测试。这不一定是坏事,但我们需要明确我们的目标。

    当对上述内容进行单元测试时,我们会为其创建单元测试

     D
     C
     B (passing in a mocked/stubbed ID)
     A (passing in mocked/stubbed IC and IB)
    

    在对A 进行单元测试时,我们不需要将来自D 的正确答案通过B 传递到A
    我们所关心的是 A 中的逻辑按预期工作,例如,A 使用参数 yz 调用 IB.x() 并返回结果IB.x()。如果我们正在检查我们是否得到了正确的答案(比如说,一个取决于D 中的逻辑的答案),那么我们已经通过了单元测试并进入了集成测试。

    底线
    被测单元是由 IOC 容器创建还是由手动创建并不重要只要我们正确隔离了被测单元。如果我们使用容器来创建对象图,那么我们进行集成测试的可能性很大(和/或存在类之间耦合过多的问题 - A 调用 IB.D.xxx()

    模拟集成测试

    警告:以下部分内容取决于所使用的 IOC 容器。使用 Unity 时,最后一次注册“获胜”。我不知道这是否适用于其他人。

    在我们所讨论的极简主义系统中

    A 
      A (IB b)
    
    B : IB
    

    B 是我们的“叶子”。它与外界对话(例如,从网络流中读取)。 在集成测试时,我们想模拟这个。

    对于实时系统,我们使用CreateContainerCore() 设置 ServiceLocator。 这包括注册IB 的“实时”实现。

    在执行需要 IB 模拟版本的集成测试时,我们使用 CreateContainerWithMockedExternalDependencies() 设置容器,该容器包装 CreateContainerCore() 并为 IB 注册一个模拟对象。

    下面的代码被大大简化了,但形状可以根据需要扩展到尽可能多的类和依赖项。在实践中,我们有一个基础测试类来帮助设置服务定位器、模拟/存根类、访问模拟以进行验证和其他内部维护(例如,每个测试都不需要显式设置 ServiceLocator 提供程序)

    [TestClass]
    public class IocIntegrationSetupFixture {
    
    
        [TestMethod]
        public void MockedB() {
    
            ServiceLocator.SetLocatorProvider(() => new UnityServiceLocator(CreateContainerWithMockedExternalDependencies()));
    
            var a = ServiceLocator.Current.GetInstance<A>();
            Assert.AreEqual("Mocked B", a.GetMessage());
    
        }
    
    
        [TestMethod]
        public void LiveB() {
    
            ServiceLocator.SetLocatorProvider(() => new UnityServiceLocator(CreateContainerCore()));
    
            var a = ServiceLocator.Current.GetInstance<A>();
            Assert.AreEqual("Live B", a.GetMessage());
        }
    
        private IUnityContainer CreateContainerCore() {
            var container = new UnityContainer();
            container.RegisterType<IB, B>(new ContainerControlledLifetimeManager());
            return container;
        }
    
        private IUnityContainer CreateContainerWithMockedExternalDependencies() {
            var container = CreateContainerCore();
    
            var mockedB = new Mock<IB>();
            mockedB.SetupGet(mk => mk.Message).Returns("Mocked B");
            container.RegisterInstance<IB>(mockedB.Object);
    
            return container;
        }
    
    
        public class A  {
    
            private IB _b;
            public A(IB b) {
                _b = b;
            }
    
            public string GetMessage() {
                return _b.Message;
            }
    
        }
    
        public interface IB {
            string Message { get; }
        }
    
        private class B : IB {
            public string Message {
                get { return "Live B"; }
            }
        }
    
    }
    

    【讨论】:

    • 我发现一篇文章说,在进行集成测试时,应该使用应用程序的 IoC 容器,以便正在测试的部分的集成与实时应用。
    • @Ian Warburton 是的,对于集成测试,我同意。我在答案中非常关注单元测试。可能会发生我们有很好的集成测试,并且通过了出色的测试,但实际的应用程序失败了,因为实时 IOC 没有正确连接。用于测试和生产的通用容器设置是好的,只要在集成测试时可以交换叶子对象 - 例如用网络服务模拟/存根替换网络服务
    • 你会怎么做?从容器设置类继承并覆盖添加叶对象的方法?
    • @Ian Warburton 更新了答案以举个例子
    • 这很有趣。因此,您正在覆盖较早的注册。我仍然更喜欢我的想法,比如说,从 CreateContainerCore 继承的 CreateContainerWithMockedExternalDependencies 和重写方法,以便使用模拟。我想那里的问题是你最终可能会得到越来越多的方法来覆盖。
    猜你喜欢
    • 2011-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-17
    • 1970-01-01
    • 2012-05-02
    相关资源
    最近更新 更多