单元测试——模拟c#中的局部变量

本文关键字:局部变量 模拟 单元测试 | 更新日期: 2023-09-27 17:50:24

我有一个c#类,它实例化自己的NetworkCommunicator类。我想为我的单元测试模拟出NetworkCommunicator类,并用一个非常简单的存根替换它。

但是NetworkCommunicator从来没有作为参数传递。它由被测试的类创建。

在Ruby中,这很容易模拟出来。在Java中,这就是你需要依赖注入的原因,它对这个项目来说太重了。有没有一个简单的方法来模拟这在c#中,也许使用Moq或类似的东西?

单元测试——模拟c#中的局部变量

您提到对于这个项目来说,DI太重量级了,为什么不试试卡车司机的DI呢?

public interface IDependency
{
    void DoSomeStuff();
}
public class ClassUnderTest
{
    private IDependency _dependency;
    public ClassUnderTest(IDependency dependency)
    {
        _dependency = dependency;
    }
    public ClassUnderTest() : this(new Dependency())
    {}
    public void ImportantStuff()
    {
        _dependency.DoSomeStuff();
    }
}

使用这种构造函数链接技术,您现在可以随心所欲地模拟独立性,而不必担心连接DI或IoC。

创建一个"TestClass",它继承于被测试的类。用模拟实例覆盖该参数在被测类上创建一个属性,返回新实例

public class ClassUnderTest {
public string MethodYouAreTesting(int someInput) {
var networkCommunicator = GetNetworkCommunicator();
// Do some stuff that I might want to test
return "foo";
}
public virtual NetworkCommunicator GetNetworkCommunicator {
    return new NetworkCommunicator();
}
}
[TestFixture]
public class ClassUnderTestTests {
public void GivenSomeCondition_MethodYouAreTesting_ReturnsFooString() {
var classToTest = new TestClassUnderTest();
var result = classToTest.MethodYouAreTesting(1);
Assert.That(result, Is.EqualTo("foo");
}
}
public class TestClassUnderTest : ClassUnderTest {
public override GetNetworkCommunicator {
return MockedNetworkCommunicator;
}
}

我在"单元测试的艺术"中读到过这种技术,当重构到完全依赖注入没有意义时,或者当我测试的类不能改变时,我经常使用它。

您应该重构代码并传入依赖项。在Visual Studio 2012中,您还可以使用typemock作为更容易使用的替代品。

这是内置的Fakes系统,在http://msdn.microsoft.com/en-us/library/hh549175.aspx

有很好的描述

如果这对你的用例来说太重了,你可能会发现PrivateObject类更有用。

我有一个c#类,它自己实例化了一个NetworkCommunicator类。

正如你所注意到的,当你想模拟这个东西时,这是c#中的show stopper。解决方案很简单,取决于实例化类的上下文/目的:
  • 如果它是可重用的组件,则将其作为依赖注入
  • 如果每次有需求时都应该创建,则通过工厂提供

无论哪种方式,都需要DI(第二个示例中的工厂也被自然注入)。

在Java中,这就是你需要依赖注入的原因,它对这个项目来说太重了。

依赖注入太重了吗?DI是一种设计模式,只有当不是真正需要时才会使用它。你的问题清楚地表明你需要它。也许你的意思是,DI容器对你的项目太重了?这可能是真的,因为根据项目的复杂性,您应该选择合适的方式来应用DI。

我想再提出一点,在应用Greg Smith的回答中提出的解决方案时要注意。本质上,你的API以构造函数结束:

public TestedClass() : this(new Dependency()) ...
public TestedClass(IDependency) ...

乍一看可能很吸引人,但当考虑到长期的观点时,几个问题就开始出现了:

  • TestedClass 必须有 IDependency还是没有它可以做得很好?
  • 什么默认(无参数构造函数)默认为(实现细节级别的知识需要正确使用它)?
  • 它创建了紧密耦合的组件(TestedClass组件可能必须引用其他组件- Dependency的组件,即使它可能与它无关)

这是一个不同名称的反模式,例如私生子注入。当然,其中一些问题可能会得到缓解(比如将构造函数设置为protected/internal或在同一程序集中使用默认实现),但是反模式及其长期后果仍然存在。还要注意,它并不比常规DI更简单、更快或更少代码。

你必须问自己什么更轻 -应用适当的DI,还是使用反模式和/或第三方框架(MS Fakes)。