Mock框架与MS Fakes框架的比较

104

对于像 NMock 和 VS 2011 Fakes Framework 这样的模拟框架之间的区别有点困惑。

通过 MSDN,我理解的是,Fakes 允许您像 RhinoMock 或 NMock 一样模拟您的依赖项,但方法不同,Fakes 生成代码来实现此功能,而 Mocks 框架则不会。所以我的理解是正确的吗?Fakes 只是另一个 Mock 框架吗?

5个回答

195

您的问题是关于MS Fakes框架与NMock有什么不同,而其他答案已经解决了一些问题,但以下是一些关于它们相同和不同之处的更多信息。NMock也类似于RhinoMocks和Moq,所以我把它们归为一类。

我看到NMock/RhinoMocks/Moq和MS Fakes Framework之间有3个主要区别:

  • MS Fakes框架使用生成的代码,就像Visual Studio早期版本中的Accessors一样,而不是通用类型。当您想要为一个依赖项使用Fakes框架时,您需要将包含该依赖项的程序集添加到测试项目的引用中,然后右键单击它以生成测试替身(存根或Shims)。然后在测试时,您实际上是使用这些生成的类。 NMock使用泛型来完成相同的事情(例如:IStudentRepository studentRepository = mocks.NewMock<IStudentRepository>())。我认为,MS Fakes框架的方法会抑制来自测试内的代码导航和重构,因为您实际上正在使用生成的类,而不是真正的接口。

  • MS Fakes框架提供了存根和Moles(Shims),而NMock、RhinoMocks和Moq都提供了存根和模拟。我真的不明白为什么MS决定不包括模拟,而且出于个人喜好的原因,我也不喜欢Moles(下文将有描述)。

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

为了澄清框架所提供的差异:NMock、RhinoMocks和Moq都提供两种类型的测试替身(存根和模拟)。Fakes框架提供了存根和Moles(它们称之为Shims),不幸的是没有包括模拟。为了理解NMock和MS Fakes之间的区别和相似之处,了解这些不同类型的测试替身是有帮助的:

存根: 当您需要为方法或属性提供值时,测试替身会被测试方法询问。例如,当我的测试方法调用IStudentRepository测试替身的DoesStudentExist()方法时,我希望它返回true。

在NMock和MS Fakes中,存根的概念是相同的,但是在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 的示例中,您为 DoesStudentExist 方法创建了一个全新的实现(请注意,它被称为 DoesStudentExistInt32,因为 fakes 框架在生成存根对象时会附加参数数据类型到方法名称,我认为这会使测试的清晰度变得不够明显)。说实话,NMock 的实现也令我感到困扰,因为它使用字符串来标识方法名。(如果我误解了 NMock 的预期使用方式,请谅解)这种方法真的会抑制重构,因此我强烈建议出于这个原因选择 RhinoMocks 或 Moq。

Mock:Mock 用于验证您正在测试的方法与其依赖之间的交互。使用 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的示例更加表达清晰。

模拟器(Shims):坦率地说,我不喜欢使用模拟器,因为它们有被滥用的潜在风险。我喜欢单元测试(尤其是TDD),其中一个原因是通过对代码进行测试,可以帮助我们了解哪些代码写得不好。因为测试糟糕的代码很困难,而使用模拟器则不是这样,因为模拟器实际上是设计用来允许您针对未注入或测试私有方法的依赖项进行测试的。它们类似于存根,只是您需要像这样使用ShimsContext:

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

我对shim的担忧在于,人们可能会认为它是“更容易进行单元测试的方法”,因为它不会强制你按照应该编写代码的方式来做。有关这个概念的更完整描述,请参阅我的这篇文章:

如果您想了解与虚拟方法相关的一些问题,请查看以下文章:

如果您有兴趣学习RhinoMocks,这里有一门Pluralsight培训视频(完全透明 - 我编写了这门课程,并获得了观看次数的版税,但我认为它适用于本讨论,所以我在这里包含它):


15
@Jim,我不同意允许“针对未注入的依赖项进行测试”是一件坏事。相反,在实践中,这种能力有助于避免在代码库中大量使用无意义的接口以及相关的“对象连接”配置/复杂性。我已经看到了一些项目(包括Java和.NET),它们具有数百个仅有一个实现类的Java/C#接口,以及大量无用的XML配置(用于Spring DI bean或在Web.config中用于MS Unity框架)。这样的依赖关系最好不要被注入。 - Rogério
20
@Rogerio,我曾经参与过多个项目,其中有数千个类都遵循了这种模式。如果依赖注入做得好(如果你正在使用XML配置,那么就不是正确的方式,我的想法是这样的),它是简单且相对轻松的。另一方面,我曾经参与过没有使用依赖注入的系统,类之间的耦合总是让我感到非常痛苦。测试并不是使用依赖注入的原因(虽然这也不是坏事)。真正的原因是减少了耦合。拥有由单个类实现的接口从未给我带来过痛苦。 - Jim Cooper
19
@Jim,我认为测试糟糕设计的能力是一个巨大的优点。在将旧代码重构为更好的设计之前,首先要做的事情就是编写测试。 - Thomas Materna
4
我的原则是只有在需要使用Shim来替换共享/静态方法或者我无法访问实现接口的类时才使用Fakes。对于其他所有情况,我都使用Moq。Fakes速度较慢(因为需要生成假程序集),并且需要更多编码工作才能匹配Moq所提供的功能。 - Nick
6
首先,我非常清楚依赖注入不需要单独的接口;我之前的评论只是在暗示使用依赖注入时常会创建这样的接口。其次,“IOC”(控制反转)与依赖注入无关!(前者是关于方法之间控制流的反转,而后者是关于为组件/对象解析依赖项。)如果你混淆了IOC和DI,那么你就是显示无知的人,而这个网站不应该让人互相侮辱,请保持文明。 - Rogério
显示剩余10条评论

26

你说得没错,但这个问题还有更多需要注意的地方。从这篇回答中最重要的几点是:

  1. 你的架构应该正确使用存根和依赖注入,而不是依赖Fake和Mock的支撑

  2. Fake和Mock在测试那些你不能或不想改变的代码时非常有用,例如:

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

Shim(在开发期间称为“Moles”)是一个通过路由调用来操作的模拟框架。它并不像其他模拟框架一样需要费力地构建模拟对象(即使使用Moq也相对费力!),Shim只需使用已经存在的生产代码对象。Shim只需将调用从生产目标重新路由到测试委托。

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

高效地实现Fake(Shim、Mock和Stub类型)需要一点时间来适应,但这样做是非常值得的。我个人通过使用Shims/Moles、Mocks和Stub类型节省了数周的开发时间。希望你也能像我一样享受这项技术带来的乐趣!


12
基于你在 Fakes 上的评论缺乏细节和术语问题,我对它进行了点踩。 Fakes 是所有类型的测试替身的通用术语,也是微软使用的其测试替身库的名称。在他们的库中,只有 Shims 实际上是 Moles,而存根则不是。关于需要示例的问题,我希望在你的回答中看到一个示例,展示如何使用 Fakes 创建一个 Shim(或 Stub)比使用 Moq 更简单。如果你修改了回答来解决这些问题,我会取消我的点踩。 - Jim Cooper
1
但是它确实为使用 shims(moles)即第三方代码,系统/SDK API 提供了很好的理由。这不仅仅涉及到当您正在使用自己的内部解决方案时。所以我会给您增加一票来平衡 :-) - Tony Wall
2
@Mike 和 Jim,我努力纠正了这个答案中使用的术语。如果有改进的地方,请告诉我。谢谢。 - Snesh

15
据我了解,Visual Studio团队希望避免与各种可用于.NET的模拟库竞争。微软经常面临这样的难题。如果他们不提供某些功能,就会受到批评(“为什么微软不提供一个模拟库呢?模拟是如此普遍的需求?”),但是如果他们提供了,就会被指责过于强势,将其自然支持者赶出市场。通常情况下,他们会决定不仅仅提供自己的替代技术而是使用现有的和广受好评的技术。在这里似乎也是如此。
Fakes的Shim特性非常实用。当然,存在一些危险性。需要一些纪律来确保你只在必要时使用它。但是,它填补了一个很大的空白。我的主要抱怨是它只能在Visual Studio 2012的Ultimate版本中获得,因此只对.NET开发社区的一部分人群可用。多么遗憾啊。

2
伪造框架也可在高级版中使用。 - AlwaysAProgrammer

13

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

Fakes提供的另一类对象被称为“Shim”,它提供了一种替换依赖项行为的机制,这些依赖项未被(或无法)充分解耦以进行标准替换模拟。据我所知,TypeMock是目前唯一一个提供此类功能的主要模拟框架之一。

顺便说一下,如果您以前尝试过Moles,那么Fakes本质上是相同的东西,最终走出了微软研究部门,成为了一个实际的产品。


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

1
关于虚假(Shim + Stub)对象,上文已经很清楚地定义了,尽管我猜最后一条评论中的最后一段总结了整个情况。
虽然很多人会认为虚假(Shim + Stub)对象在某些单元测试案例中是不错的资产,但缺点是无论您使用Visual Studio 2012还是Visual Studio 2013,这些选项仅与Premium或Ultimate版本一起提供。这意味着,您将无法在任何Pro版本上运行那些Fakes(Shim + Stub)。
您可能会在Pro版本上看到Fakes(Shim + Stub)菜单选项,但请注意,有相当大的机会会得到绝对没有任何东西的结果......它不会生成任何编译错误告诉您缺少重要内容,只是没有那些选项,所以不要浪费时间......
这是一个开发团队需要考虑的重要因素,特别是如果其中一个人是唯一使用Ultimate版本而其他人使用Pro版本......另一方面,Moq可以轻松地通过Nuget安装,无论您使用哪个Visual Studio版本。我使用Moq没有任何问题,任何工具的关键是要知道它们用于什么,如何正确使用 ;)

1
请将您的问题分成较小的段落,以便更容易阅读。 - user456814
@SirXamelot,如果不重构代码,就无法更改 .NET 提供的静态调用(例如 DateTime.Now 或 Guid.NewGuid)的输出结果。 - zaitsman
我曾多次向微软博客提出意见,认为将MS.Fakes仅限于企业版会阻碍它成为主流产品。正如你所说,Moq在专业版和Gitlab测试管道中运行良好。 - EricBDev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接