102

对 NMock 与 VS 2011 Fakes Framework 等 Mock 框架的差异有点困惑。通过 MSDN,我了解到 Fakes 允许您像 RhinoMock 或 NMock 一样模拟您的依赖项,但是方法不同,Fakes 生成代码来实现此功能,但 Mocks 框架没有。那么我的理解正确吗?Fakes 只是另一个 Mock 框架吗

4

5 回答 5

195

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

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

  • MS fakes 框架使用生成的代码,就像以前版本的 Visual Studio 中的访问器而不是泛型类型。当您想为依赖项使用 fakes 框架时,将包含依赖项的程序集添加到测试项目的引用中,然后右键单击它以生成测试替身(存根或垫片)。然后,当您进行测试时,您实际上是在使用这些生成的类。NMock 使用泛型来完成同样的事情(即IStudentRepository studentRepository = mocks.NewMock<IStudentRepository>())。在我看来,MS Fakes 框架方法抑制了测试中的代码导航和重构,因为您实际上是在针对生成的类而不是您的真实界面工作。

  • MS fakes 框架提供 stub 和 moles(垫片),而 NMock、RhinoMocks 和 Moq 都提供 stub 和mock。我真的不明白 MS 决定不包括模拟,我个人不是痣的粉丝,原因如下所述。

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

为了阐明框架提供的差异:NMock、RhinoMocks 和 Moq 都提供了两种类型的测试替身(存根和模拟)。fakes 框架提供了 stub 和 moles(他们称之为 shims),不幸的是不包括 mocks。为了了解 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 而不是 NMock。

Mocks: Mocks 用于验证被测方法与其依赖项之间的交互。使用 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,示例更具表现力。

痣(垫片):坦率地说,我不喜欢痣,因为它们可能会被滥用。我非常喜欢单元测试(尤其是 TDD)的一件事是测试你的代码可以帮助你了解你在哪里编写了糟糕的代码。这是因为测试写得不好的代码很困难。在使用 moles 时情况并非如此,因为 moles 实际上旨在允许您针对未注入的依赖项进行测试或测试私有方法。它们的工作方式与存根类似,不同之处在于您使用如下 ShimsContext:

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

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

有关与假货框架相关的一些问题的更多信息,请查看以下帖子:

如果你对学习 RhinoMocks 感兴趣,这里有一个 Pluralsight 培训视频(完全公开 - 我写了这门课程,并获得了观看的版税,但我认为它适用于这个讨论,所以我把它包括在这里):

于 2012-09-27T16:10:08.633 回答
25

你是对的,但还有更多的故事。从这个答案中最重要的事情是:

  1. 您的架构应该正确使用存根和依赖注入,而不是依赖于 Fakes 和 mocks

  2. 伪造和模拟对于测试您不应该或不能更改的代码很有用,例如:

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

Shims(在开发过程中称为“Moles”)确实是一个模拟框架,通过迂回调用来运行。垫片无需费力地构建模拟(是的,即使使用 Moq 也相对痛苦!),垫片只需使用已经到位的生产代码对象。Shims 只是将调用从生产目标重新路由到测试委托。

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

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

于 2012-07-25T16:48:56.783 回答
15

据我了解,Visual Studio 团队希望避免与可用于 .NET 的各种模拟库竞争。MS 经常面临这样的艰难决定。如果它们不提供某些功能(“为什么 MS 不为我们提供模拟库;模拟是一种如此普遍的要求?”),它们就会被诅咒(“为什么微软如此积极地采取行动并推动它市场之外的自然支持者?”)很多时候,但并非总是如此,他们决定阻止简单地提供自己的替代品来替代可用且广受欢迎的技术。这里似乎就是这种情况。

Fakes 的 shim 功能非常非常有用。当然,有危险。需要一些纪律来确保您只在必要时使用它。但是,它填补了一个很大的空白。我的主要抱怨是它仅与 VS 2012 的终极版一起提供,因此仅适用于 .NET 开发社区的一部分。太遗憾了。

于 2012-10-06T22:58:01.717 回答
13

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

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

顺便说一句,如果你之前尝试过 Moles,Fakes 本质上是一样的,最终从 Microsoft Research 中脱颖而出,成为实际产品。

于 2012-03-13T13:21:38.433 回答
1

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

尽管很多人会争辩说 Fake (Shim + Stub) 对象在某些单元测试用例中是很好的资产,但缺点是无论您使用的是 Visual Studio 2012 还是 Visual Studio 2013,这些选项仅可用使用高级版或终极版。IOW,这意味着您将不会在任何 Pro 版本上运行任何这些 Fakes(Shim + Stub)。

您可能会在 Pro 版本上看到 Fakes (Shim + Stub) 菜单选项,但请注意,您很有可能最终会一无所获……它不会产生任何编译错误,告诉您某些重要的事情丢失,选项不存在,所以不要浪费你的时间......

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

于 2014-07-05T19:54:39.227 回答