【问题标题】:Mocking out a local variable in C#在 C# 中模拟一个局部变量
【发布时间】:2012-12-15 06:51:03
【问题描述】:

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

NetworkCommunicator 永远不会作为参数传递。它是由正在测试的类创建的。

在 Ruby 中,这很容易模拟出来。在 Java 中,这就是为什么你需要依赖注入,这对于这个项目来说太重了。有没有一种简单的方法可以在 C# 中模拟它,可能使用 Moq 或类似的东西?

【问题讨论】:

  • 注射什么东西为什么那么重??

标签: c# unit-testing dependency-injection mocking moq


【解决方案1】:

您提到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();
    }
}

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

【讨论】:

  • 可以使用构造函数 shim,它可以模拟内部新建的类 :-)
  • 是的,有几种方法可以做到这一点,但我个人认为,当 DI 如此简单时,尝试模拟内部是一个坏主意......
  • 这种方法称为bastard injection(又名穷人的DI)。虽然被认为是一种反模式,但我也建议在这种情况下从它开始(所以答案是 +1)。对于任何项目来说,依赖注入绝对不会太重。另一方面,对于项目而言,使用 DI 框架可能“过于繁重”。
【解决方案2】:

创建一个继承自被测类的“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;
}

}

我在“单元测试的艺术”中读到了这种技术,并在重构为完全 DI 没有意义或我正在测试的类不是我可以改变的东西时经常使用它。

希望这会有所帮助。

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • +1 用于重构代码;如果你的类很难测试,那么让它们更容易测试
    • @Steven,有时重构工作对于大型遗留项目来说过于昂贵,团队别无选择,只能使用 typemock of fakes 框架之类的工具。
    【解决方案4】:

    有内置的 Fakes 系统,在 http://msdn.microsoft.com/en-us/library/hh549175.aspx 上描述得很好

    如果这对于您的用例来说过于繁重,您可能会发现 PrivateObject 类更有用。

    【讨论】:

      【解决方案5】:

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

      正如您所注意到的,当您想模拟这个东西时,这在 C# 中是一个表演障碍。解决方案很简单,取决于实例化类的上下文/目的:

      • 如果它是可重用组件,则将其作为依赖项注入
      • 如果每次有需求时都应该创建它,则通过工厂提供它

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

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

      依赖注入是不是太重了? DI is design pattern,它只是在不是真的需要时使用时太重了。你的问题清楚地表明你需要它。也许您的意思是 DI 容器 对您的项目来说太重了?这可能是真的,因为根据项目的复杂性,您应该选择appropriate way to apply DI

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

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

      乍看之下很吸引人,但如果考虑到长远的眼光,就会出现几个问题:

      • TestedClass 必须有 IDependency 还是没有它也能正常工作?
      • 什么默认(无参数构造函数)默认为(实现细节级别的知识才能正确使用它)?
      • 它创建紧密耦合的组件(TestedClass 程序集可能必须引用其他程序集 - Dependency 的程序集,即使它可能与它无关)

      这是一个使用不同名称的反模式,例如Bastard Injection。当然,其中一些问题可能会得到缓解(例如使构造函数受保护/内部或在同一程序集中具有默认实现),但反模式及其长期后果仍然存在。另请注意,它绝不比常规 DI 更简单、更快或更少代码。

      您必须问自己什么不那么繁重 - 应用适当的 DI,或者使用反模式和/或 3rd 方框架 (MS Fakes)。

      【讨论】:

      • 你刚刚成功了。这是这个问题的答案。我希望这将被接受为答案。
      • 我同意在有任何复杂程度的情况下,我的建议不是可行的方法,但在这种情况下,要求似乎是快速且廉价地获得可测试的类,在这种情况下,我个人认为穷人的注射通常没问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-08-03
      • 1970-01-01
      • 2021-11-19
      • 2010-10-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多