我如何模拟没有接口的类?


81

我正在Windows 7中使用C#在.NET 4.0上工作。

我想使用模拟测试某些方法之间的通信。唯一的问题是我想在不实现接口的情况下做到这一点。那可能吗?

我刚刚阅读了很多关于模拟对象的主题和一些教程,但是它们全部都用于模拟接口,而不是类。我尝试使用Rhino和Moq框架。


1
这些工具是从专门使用“ IInterfaces”的角度创建的,这一点确实令人吃惊。
AR

4
假设您正在使用基于接口的DI创建。如今,这是一个非常标准的模式。
Maess 2013年

不幸的是,该模式与不可变类型“模式”冲突:(
Matthew Watson

Answers:


65

只需将您需要伪造的任何方法标记为virtual(而非私有)即可。然后,您将能够创建可以覆盖该方法的伪造品。

如果使用new Mock<Type>并且没有无参数构造函数,则可以将参数作为上述调用的参数传递,因为它采用了param Objects


1
这使我想知道是否有必要为我要模拟的每个类创建一个接口。如果它们没有接口,我们是否只能使用具体的类进行模拟?
Orad 2015年

13
@orad实际上,我倾向于首先创建一个类,仅在需要打破通用功能时才创建一个接口。
Justin Pihony 2015年

然后,如何将模拟传递给需要指定类型的对象?如果我创建一个模拟'new Mock <MyType>'并尝试将其传递给期望MyType的对象,则会出现错误“无法将Mock <MyType>转换为MyType”。
中微子'18

2
我已经解决了,如果您将模拟定义为“ var myMock = new Mock <MyType>()”,则必须将其传递给使用模拟作为myMock.Object的类。
中微子'18

4
当没有无参数的构造函数并且封闭的Concrete类中的带参数的构造函数不是公共的时,就会出现主要问题。(在这里,“封闭”是指在外部包装中)。这样,就不能实现接口,不能使该方法虚拟化,也不能使用参数化的构造函数。
Abhilash Pokhriyal

22

大多数模拟框架(包括Moq和RhinoMocks)都会生成代理类来代替模拟类,并使用定义的行为覆盖虚拟方法。因此,您只能在具体或抽象类上模拟接口或虚拟方法。另外,如果要模拟具体的类,则几乎总是需要提供无参数的构造函数,以便模拟框架知道如何实例化该类。

为什么不喜欢在代码中创建接口?


101
因为它用大量的接口阻塞了代码库,如果不是为了测试框架的某些技术限制,那将完全没有必要吗?
BlueRaja-Danny Pflughoeft

9
您已经听说过“触手可及”的表达。这就解释了为什么开发人员总是抱怨。C#语言在设计时并未考虑单元测试。这就是为什么要在没有大量毫无意义的接口或虚拟方法的情况下注入模拟依赖项真的很困难的原因。也许有人很快就会发明一种易于单元测试的语言。届时,我们还有其他事情要抱怨。
约翰·亨克尔

13
我很高兴知道我不是唯一将接口和虚拟方法视为混乱的人,如果它们的唯一目的是服务于测试。
aaaaaa

13
“唯一的目的是为测试服务”,但是创建可测试的代码对您来说不重要吗?
MakkyNZ

12
“为什么不喜欢在代码中创建接口?” 因为我没有写这个对象,所以我没有访问代码的权限。我需要创建一个我不拥有的,没有实现接口的封闭对象的模拟。
肖恩·沃勒

16

使用MoQ,您可以模拟具体的类:

var mocked = new Mock<MyConcreteClass>();

但这使您可以覆盖virtual代码(方法和属性)。


1
我试过了,但是当我运行测试项目时,程序会抛出异常:“无法实例化类的代理”“找不到无参数的构造函数。”
Vinicius Seganfredo

2
只需将构造函数参数传递给Mock <>构造函数即可。EGnew Mock<MyConcreteClass>(param1, anotherParam, thirdParam, evenMoreParams);
Bozhidar Stoyneff

1
为什么不只为该类创建一个接口?
罗伯特·佩里

11

我认为最好为该类创建一个接口。并使用界面创建单元测试。

如果您无权访问该类,则可以为该类创建适配器。

例如:

public class RealClass
{
    int DoSomething(string input)
    {
        // real implementation here
    }
}

public interface IRealClassAdapter
{
    int DoSomething(string input);
}

public class RealClassAdapter : IRealClassAdapter
{
    readonly RealClass _realClass;

    public RealClassAdapter() => _realClass = new RealClass();

    int DoSomething(string input) => _realClass.DoSomething(input);
}

这样,您可以使用IRealClassAdapter轻松为类创建模拟。

希望它能工作。


4
这对于维护来说很糟糕。如果要向RealClass添加新方法,则还必须将其添加到IReadClassAdapter和RealClassAdapter。三倍的努力!更好的解决方案是在RealClass中的每个公共方法上添加“虚拟”关键字。
约翰·亨克尔

12
@JohnHenckel我假设我们没有访问RealClass的权限。因此,在我看来,添加“虚拟”方法不是一种选择。如果您可以访问真实的类,则最好直接实现与新类的接口,我认为这是最佳实践。
阿南·萨特里亚

谢谢。这似乎是无权模拟类的唯一合法解决方案。即使有很多“愚蠢”的代码,我也需要它来在“不合适的环境”中运行代码
Michael Spannbauer

7

标准的模拟框架正在创建代理类。这就是为什么它们在技术上仅限于接口和虚拟方法的原因。

如果您还想模拟“常规”方法,则需要一个可与检测配合使用而不是生成代理的工具。例如,MS Moles和Typemock可以做到这一点。但是前者有一个可怕的“ API”,而后者是商业化的。


我们可以使用该公共方法模拟普通类吗?
SivaRajini


2

如果情况变得更糟,则可以创建接口和适配器对。您将更改对ConcreteClass的所有使用以改为使用接口,并始终在生产代码中传递适配器而不是具体类。

适配器实现接口,因此模拟程序也可以实现接口。

它不仅仅是将方法虚拟化或仅添加接口而已,但如果您无权访问具体类的源代码,则可能会使您陷入困境。


1

在我从事的一个旧项目中,我遇到了类似的问题,其中不包含任何接口或最佳实践,而且由于项目业务的成熟,很难再次强制他们重新构建内容或重构代码,因此在我的UnitTest项目中,我曾经在要模拟的类和该包装器实现接口上创建了一个包装器,该接口包含了我想要设置和使用的所有所需方法,现在我可以模拟包装器而不是真实类。

例如:

您要测试的服务不包含虚拟方法或实现接口

public class ServiceA{

public void A(){}

public String B(){}

}

包装起订量

public class ServiceAWrapper : IServiceAWrapper{

public void A(){}

public String B(){}

}

包装器界面

public interface IServiceAWrapper{

void A();

String B();

}

在单元测试中,您现在可以模拟包装器:

    public void A_Run_ChangeStateOfX()
    {
    var moq = new Mock<IServiceAWrapper>();
    moq.Setup(...);
    }

这可能不是最佳实践,但是如果您的项目规则以这种方式强迫您执行此操作。还要将所有包装器放入仅为单元测试指定的单元测试项目或帮助器项目中,以免不必要的包装器或适配器使项目过载。

更新: 这个答案来自一年多的时间,但是在今年,我遇到了许多使用不同解决方案的类似情况。例如,使用Microsoft Fake Framework创建模拟,伪造品和存根,甚至测试没有任何接口的私有方法和受保护方法非常容易。您可以阅读:https : //docs.microsoft.com/zh-cn/visualstudio/test/isolate-code-under-test-with-microsoft-fakes?view=vs-2017

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.