【问题标题】:How to mock a class with no interface and no virtual methods in C#?如何在 C# 中模拟没有接口和虚拟方法的类?
【发布时间】:2016-09-06 19:50:42
【问题描述】:

我正在为别人的代码编写单元测试,我不允许修改。

说我有:

class BadClass
{
    public BadClass()
    {
        // the service isn't going to be running during testing
        // it also has no interface
        someFlag = AGloballyStoredService.getSomeFlag();
    }

    public bool someFlag;

}

使用者:

class AnotherBadClass
{
    public AnotherBadClass(BadClass badClass)
    {
        someFlag = badClass.someFlag;
    }
    public bool someFlag;
}

说我想要测试:

public void AnotherBadClassConstructorTest()
{
    BadClass badClass = new BadClass();
    AnotherBadClass anotherBadClass = new AnotherBadClass(badClass);
    Assert.IsNotNull(anotherBadClass);
}

我想模拟 BadClass,但它没有接口,如果服务没有运行,它会在其构造函数中失败。

考虑到我无法修改我正在测试的代码,有没有一种简单的方法可以使这项工作正常进行?

如果归根结底,我可以告诉他们,他们要么让我修改现有的代码库,要么接受这个模块低于 10% 的覆盖率。

【问题讨论】:

  • 您可能需要使用像 Microsoft Fakes 这样的专用库来拦截并重写方法以将它们重定向到您的模拟。
  • 如果你不能接触代码,恐怕只有集成测试才是真正的选择。

标签: c# unit-testing


【解决方案1】:

考虑到我无法修改我正在测试的代码,是否有 实现这项工作的直接方法?

没有。但是,我看到了两个不直截了当的选择:

A) 使用Microsoft Fakes 通过重写库来提供模拟。您不必修改代码。示例:您可以修改静态 DateTime.Now 以返回您选择的日期,如下所示:

System.Fakes.ShimDateTime.NowGet = 
() =>
{ return new DateTime(fixedYear, 1, 1); };

B) 你可以尝试继承被测试的类并覆盖使用的方法/ctors。不过有两个限制。您必须能够用您的模拟对象实例化/交换测试类的实例(通过使用反射,您可以获得私有成员)并且模拟方法必须是虚拟的。在您的情况下,您将创建:

class BadClassMock : BadClass
{
    public BadClassMock()
    {
    }

    public bool someFlag;

    public void InitForTest()
    {
        someFlag = true;
    }
}

只要您不调用基本构造函数,它就会起作用。您可以从这里使用讨厌的 FormatterServices.GetUninitializedObject() 技巧:Creating instance of type without default constructor in C# using reflection

及以后:

public void AnotherBadClassConstructorTest()
{
    BadClassMock badClassMock = (BadClassMock)FormatterServices.GetUninitializedObject(typeof(BadClassMock));
    badClassMock.InitForTest();
    AnotherBadClass anotherBadClass = new AnotherBadClass(badClassMock);
    Assert.IsNotNull(anotherBadClass);
}

是的,这是一个很大的解决方法。显然最好的办法是说服作者修改代码。

【讨论】:

  • 这可行,但看起来我可能无法引入假货。不知道 Moq 中是否有类似的功能?
  • 起订量在不同级别上工作。 Moq 允许您使用继承、虚拟方法或接口创建模拟 - 它不适用于已编译的代码,因此它不允许您模拟 BadClass。使用 Fakes,您只需将 BadClass 的编译代码交换为您想要的任何内容。您仍然可以尝试 B 选项。
【解决方案2】:

如果您不想更改代码以接收接口,则可以使用TypeMockJustMock 来模拟它。两者都是付费工具,我还没有找到可以做你想做的免费工具。我更喜欢 JustMock。 JustMock 的语法如下所示:

public void AnotherBadClassConstructorTest()
{
    BadClassMock badClassMock = Mock.Create<BadClassMock>();
    AnotherBadClass anotherBadClass = new AnotherBadClass(badClassMock);
    Assert.IsNotNull(anotherBadClass);
}

如果您要经常这样做或想要测试没有考虑单元测试的遗留代码,我认为这些工具非常值得。您也可以随时免费试用。

【讨论】:

    【解决方案3】:

    实际上,您可以在不修改代码的情况下模拟BadClass,通过使用TypeMock Isolator,您将能够模拟一个类,即使它不是虚拟或接口。 例如,这是您尝试创建的测试:

    [TestMethod]
    public void CreateAnotherBadClass_IsNotNull()
    {
        var fakeBadClass = Isolate.Fake.Instance<BadClass>();
        var anotherBadClass = new AnotherBadClass(fakeBadClass);
    
        Assert.IsNotNull(anotherBadClass);
    }
    

    在这个测试中,我创建了一个BadClass 的假实例并将其作为参数发送给AnotherBadClass c'tor。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-25
      • 1970-01-01
      • 1970-01-01
      • 2012-07-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多