模拟框架与MS Fakes框架


99

对于Mock框架(例如NMock)与VS 2011 Fakes框架的区别有些困惑。通过MSDN,我了解到Fakes允许您像RhinoMock或NMock一样模拟依赖项,但是方法不同,Fakes生成代码以实现此功能,而Mocks框架则不行。我的理解正确吗?是假货只是另一个Mock框架

Answers:


189

您的问题是关于MS Fakes框架与NMock有何不同,看来其他答案已经解决了其中的一些问题,但是这里有一些有关它们如何相同和如何不同的更多信息。NMock也类似于RhinoMocks和Moq,因此我将它们与NMock分组在一起。

我立即看到NMock / RhinoMocks / Moq与MS Fakes框架之间存在3个主要差异:

  • MS伪造框架使用生成的代码,就像在Visual Studio早期版本中的Accessors一样,而不是通用类型。当您要使用伪造的框架作为依赖项时,可以将包含依赖项的程序集添加到测试项目的引用中,然后右键单击它以生成测试双精度(残桩或垫片)。然后,当您进行测试时,实际上是在使用这些生成的类。NMock使用泛型来完成同一件事(即IStudentRepository studentRepository = mocks.NewMock<IStudentRepository>())。我认为,MS Fakes框架方法禁止在测试中进行代码导航和重构,因为您实际上是在针对生成的类而不是实际界面进行操作。

  • MS伪造框架提供存根和痣(垫片),而NMock,RhinoMocks和Moq都提供存根和模拟物。我真的不理解MS决定不包含模拟游戏的决定,而且我个人出于以下原因不喜欢黑痣。

  • 使用MS Fake框架,您可以提供要存根的方法的替代实现。在这些替代实现中,您可以指定返回值并跟踪有关如何或是否调用该方法的信息。使用NMock,RhinoMocks和Moq,您可以生成一个模拟对象,然后使用该对象指定存根返回值或跟踪交互(是否以及如何调用方法)。我发现MS的伪造方法更加复杂且表达能力较差。

为了澄清框架提供的差异:NMock,RhinoMocks和Moq都提供两种类型的测试双打(存根和模拟)。伪造品框架提供存根和痣(它们称它们为垫片),但不幸的是不包含模拟。为了了解NMock和MS Fakes之间的区别和相似之处,了解以下几种不同的测试重复类型是有帮助的:

存根(Stub):当您需要提供方法或属性的值时,将使用存根( Stub),该值将由被测方法要求测试倍数。例如,当我的被测试方法调用IStudentRepository测试double的DidSStudentExist()方法时,我希望它返回true。

NMock和MS假冒中的存根的想法是相同的,但是使用NMock,您将执行以下操作:

Stub.On(mockStudentRepository).Method("DoesStudentExist").Will(Return.Value(true));

借助MSFakes,您可以像这样进行操作:

IStudentRepository studentRepository = new DataAccess.Fakes.StubIStudentRepository() // Generated by Fakes.
{
    DoesStudentExistInt32 = (studentId) => { return new Student(); }
};

注意,在“ MS Fakes”示例中,您为DosStudentExist方法创建了一个全新的实现(请注意,它被称为“ DoesStudentExistInt32”,因为在其生成存根对象时,fakes框架会将参数数据类型附加到方法名称中,我认为这使测试)。老实说,NMock实现也使我感到烦恼,因为它使用字符串来标识方法名称。(如果我误解了NMock的预期用途,请原谅我。)这种方法确实抑制了重构,因此,我强烈建议使用RhinoMocks或Moq而不是NMock。

模仿:模仿用于验证被测方法及其依赖项之间的交互。使用NMock,您可以通过设置类似于以下内容的期望值来实现:

Expect.Once.On(mockStudentRepository).Method("Find").With(123);

这是为什么我更喜欢RhinoMocks和Moq而不是NMock的另一个原因,NMock使用了较早的期望样式,而RhinoMocks和Moq都支持Arrange / Act / Assert方法,在这种方法中,您可以像这样在测试结束时将期望的交互指定为断言:

stubStudentRepository.AssertWasCalled( x => x.Find(123));

再次注意,RhinoMocks使用lambda而不是字符串来标识该方法。ms fakes框架根本不提供模拟。这意味着在存根实现中(请参阅上面的存根描述),您必须设置变量,以后再验证它们是否正确设置。看起来像这样:

bool wasFindCalled = false;

IStudentRepository studentRepository = new DataAccess.Fakes.StubIStudentRepository() 
{
    DoesStudentExistInt32 = (studentId) => 
        { 
            wasFindCalled = true;
            return new Student(); 
        }
};

classUnderTest.MethodUnderTest();

Assert.IsTrue(wasFindCalled);

我发现这种方法有些复杂,因为您必须在存根中跟踪调用,然后在测试中断言它。我发现NMock(尤其是RhinoMocks)的示例更具表现力。

les鼠:说实话,我不喜欢mole鼠,因为它们有可能被滥用。我非常喜欢单元测试(尤其是TDD)的一件事是,测试代码可以帮助您了解编写不良代码的位置。这是因为测试写得不好的代码很困难。使用摩尔时,情况并非如此,因为摩尔实际上是设计用来允许您针对未注入的依赖项进行测试或测试私有方法。它们的工作方式与存根类似,不同的是您使用ShimsContext这样:

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

我对垫片的担心是,人们会开始将它们视为“一种更简单的单元测试方法”,因为它不会强迫您按照应有的方式编写代码。有关此概念的更完整文章,请参阅我的这篇文章:

有关与伪造框架有关的某些问题的更多信息,请查看以下文章:

如果您有兴趣学习RhinoMocks,请观看以下Pluralsight培训视频(完整披露-我编写了本课程,并获得了稿酬,以获取观点,但我认为它适用于本次讨论,因此在此进行了介绍):


14
@Jim我不同意让一个人“针对未注入的依赖项进行测试”是一件坏事。相反,在实践中,此功能有助于避免使具有许多无意义接口和相关“对象接线”配置/复杂性的代码库变得混乱。我已经看到了几个项目(Java和.NET),它们具有数百个Java / C#接口,只有一个实现类,并且有许多无用的XML配置(用于Spring DI bean,或用于MS Unity的Web.config中)。框架)。最好不要注入此类依赖项。
罗杰里奥

19
@Rogerio,我已经在多个项目中工作了数千个遵循该模型的类,并且正确完成了依赖项注入(恕我直言,如果您使用的是XML配置,那么做错了,恕我直言),它是简单且相对轻松的。另一方面,我在不这样做的系统上工作,而类之间的耦合总是给我带来极大的痛苦。测试不是使用依赖项注入的原因(尽管这不是一个坏原因)。真正的原因是联轴器松动。具有由单个类实现的接口从未使我感到痛苦。
吉姆·库珀

17
@Jim,我认为测试不良设计的能力是一个不错的选择。在将遗留代码重构为更好的设计之前,您要做的第一件事是编写测试。
Thomas Materna

4
我的规则是仅当必须Shim共享/静态方法或无权实现接口的类时,才使用Fakes。对于其他一切,我都使用Moq。伪造的速度较慢(因为需要生成伪造的程序集),并且需要更多的编码工作才能匹配Moq所提供的功能。
尼克

5
@所有的CarloV.Dango首先,我很清楚地知道依赖注入并没有要求独立的接口; 我之前的评论只是暗示使用DI时会创建此类接口的趋势。其次,“ IOC”(控制反转)与DI 无关!(第一个是关于方法之间的控制流的倒置,第二个是关于组件/对象的依赖关系的解决。)通过混淆IOC和DI,是您表现出的是无知,而不是我。而且,坦率地说,该网站不应该受到侮辱他人的人们,因此,请让其保持文明。
罗杰里奥

23

您是正确的,但故事还有很多。要摆脱此答案,最重要的事情是:

  1. 您的架构应适当使用存根和依赖项注入,而不是依赖Fakes和嘲笑的拐杖

  2. 伪造和模拟对测试不应更改的代码非常有用,例如:

    • 不使用(或有效使用)存根的旧版代码
    • 第三方API
    • 您没有源代码的资源

Shims(在开发过程中被称为“ Moles”)确实是通过绕行调用而运行的模拟框架。填充程序无需费力地构建模拟程序(是的,即使使用Moq也比较麻烦!),而是简单地使用已经存在的生产代码对象。Shims只需将呼叫从生产目标重新路由到测试代表。

存根是从目标项目中的接口生成的。Stub对象就是接口的实现。使用Stub类型的好处是,您可以快速生成一个Stub,而不会因许多一次性使用Stub造成测试项目混乱,更不用说浪费时间创建它们了。当然,您仍然应该创建具体的存根,以便在许多测试中使用。

有效地实现伪造(Shims,Mocks和Stub类型)需要一点时间来适应,但值得付出努力。通过使用Shims / Mole,Mocks和Stub类型,我个人节省了数周的开发时间。希望您能像我一样享受这项技术带来的乐趣!


11
由于缺乏细节和您对假货的评论而出现一些术语问题,因此予以否决。假货是所有​​类型的测试双打的通用术语,也是其假库使用的名称MS。在他们的图书馆中,只有Shims实际上是Mo鼠,而存根不是。关于示例的需要,我想在您的答案中看到一个示例,该示例说明使用Fakes创建垫片(或桩)比使用Moq更简单。如果您更改答案以解决这些问题,我将更改我的不赞成票。
Jim Cooper

1
但是确实增加了使用垫片(摩尔)(即第三方代码,系统/ SDK API)的合理性。这不仅与您使用自己的内部解决方案有关。因此,我给你投一票,以实现这一目标:-)
Tony Wall

2
@Mike和Jim ..我尽力纠正此答案中使用的术语。请让我知道我们是否可以做得更好。谢谢。
Snesh 2014年

15

据我了解,Visual Studio团队希望避免与.NET可用的各种模拟库竞争。MS经常会面临这样艰难的决定。如果他们不提供某些功能(“为什么MS不向我们提供模拟库;模拟是这么普遍的要求?”),那么就被谴责,如果不这样做(“为什么Microsoft如此积极地采取行动并推动它的发展,为什么?天生支持者退出市场?”)很多时候(但并非总是如此),他们决定阻止仅仅提供自己的替代方案来替代现有和广受欢迎的技术。这里似乎就是这种情况。

伪造的伪装功能确实非常有用。当然,有危险。需要一定的纪律以确保仅在必要时使用此功能。但是,它填补了很大的空白。我的主要抱怨是,它仅随VS 2012 Ultimate版一起提供,因此仅适用于.NET开发社区的一个子部分。太遗憾了。


2
高级版也提供了fakes框架。
AlwaysAProgrammer 2014年

13

伪造包括两种不同的“伪造”对象。第一个称为“存根”,本质上是一个自动生成的虚拟对象,其默认行为可以(并且通常会)被覆盖以使其更有趣。但是,它确实缺少大多数当前可用的模拟框架提供的某些功能。例如,如果要检查存根实例上的方法是否已调用,则需要自己添加逻辑。基本上,如果您现在手动编写自己的模拟,存根可能看起来是一种改进。但是,如果您已经在使用功能更全的模拟框架,则可能会感觉Fakes存根中缺少一些重要的部分。

Fakes提供的另一类对象称为“填充程序”,它公开了一种机制,用于替换尚未(或无法)解耦的依赖项行为,以通过模拟进行标准替换。AFAIK,TypeMock是当前提供此类功能的唯一主要模拟框架之一。

顺便说一句,如果您以前尝试过Moles,那么Fakes本质上是一回事,最终从Microsoft Research进入了实际产品。


1
更新:Moles现在已集成到MS Fakes中。“ Visual Studio 2012中的Fakes框架是Moles&Stubs的下一代。但是,Fakes与Moles不同,因此,从Moles迁移到Fakes将需要对代码进行一些修改。VisualStudio 2012将不支持Moles框架。 。” 资料来源:research.microsoft.com/en-us/projects/moles
2014年

1

关于假(Shim + Stub)对象,上面已经对其进行了很好的定义,尽管我想最后一条评论中的最后一段很好地总结了整个情况。

尽管很多人会认为伪(Shim + Stub)对象是某些单元测试用例中的好​​资产,但缺点是,无论您使用的是Visual Studio 2012还是Visual Studio 2013,这些选项都仅可用具有Premium或Ultimate版本。IOW,这意味着您将不会在任何Pro版本上运行任何这些假货(Shim + Stub)。

您可能会在Pro版本上看到Fakes(Shim + Stub)菜单选项,但是请注意,很有可能您最终将一无所有...它不会产生任何编译错误,告诉您一些重要信息缺少,选项不存在,所以不要浪费时间...

这是在开发团队中考虑的重要因素,特别是如果一个人是唯一使用Ultimate版本的人,而其他人都使用Pro版本...另一方面,无论您使用哪个Visual Studio版本,都可以通过Nuget轻松安装Moq。我使用Moq没问题,任何工具的关键是要知道它们的用途以及如何正确使用它们;)


1
请将您的问题格式化为较小的段落,以便于阅读。

@SirXamelot,不重构代码,您无法更改.net提供的静态调用(例如DateTime.Now或Guid.NewGuid)的输出
zaitsman 2014年
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.