对于像 NMock 和 VS 2011 Fakes Framework 这样的模拟框架之间的区别有点困惑。
通过 MSDN,我理解的是,Fakes 允许您像 RhinoMock 或 NMock 一样模拟您的依赖项,但方法不同,Fakes 生成代码来实现此功能,而 Mocks 框架则不会。所以我的理解是正确的吗?Fakes 只是另一个 Mock 框架吗?
对于像 NMock 和 VS 2011 Fakes Framework 这样的模拟框架之间的区别有点困惑。
通过 MSDN,我理解的是,Fakes 允许您像 RhinoMock 或 NMock 一样模拟您的依赖项,但方法不同,Fakes 生成代码来实现此功能,而 Mocks 框架则不会。所以我的理解是正确的吗?Fakes 只是另一个 Mock 框架吗?
您的问题是关于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培训视频(完全透明 - 我编写了这门课程,并获得了观看次数的版税,但我认为它适用于本讨论,所以我在这里包含它):
你说得没错,但这个问题还有更多需要注意的地方。从这篇回答中最重要的几点是:
你的架构应该正确使用存根和依赖注入,而不是依赖Fake和Mock的支撑
Fake和Mock在测试那些你不能或不想改变的代码时非常有用,例如:
Shim(在开发期间称为“Moles”)是一个通过路由调用来操作的模拟框架。它并不像其他模拟框架一样需要费力地构建模拟对象(即使使用Moq也相对费力!),Shim只需使用已经存在的生产代码对象。Shim只需将调用从生产目标重新路由到测试委托。
存根是从目标项目中的接口生成的。存根对象就是接口的实现。使用Stub类型的好处是可以快速生成一个存根,而不会在测试项目中堆积许多一次性使用的存根,更不用浪费时间去创建它们。当然,你仍然应该创建具体的存根,以供许多测试使用。
高效地实现Fake(Shim、Mock和Stub类型)需要一点时间来适应,但这样做是非常值得的。我个人通过使用Shims/Moles、Mocks和Stub类型节省了数周的开发时间。希望你也能像我一样享受这项技术带来的乐趣!
伪对象包括两种不同的“伪”对象。第一种被称为“存根”,实质上是一个自动生成的虚拟对象,其默认行为可以(并且通常会)被覆盖以使其成为更有趣的模拟对象。但是,它确实缺少大多数当前可用的模拟框架提供的一些功能。例如,如果您想检查存根实例上的某个方法是否被调用,则需要自己添加此逻辑。基本上,如果您现在手动编写自己的模拟对象,那么存根可能看起来像是一个改进。然而,如果您已经使用了一个更全面的模拟框架,您可能会感到Fakes stubs缺少一些重要的部分。
Fakes提供的另一类对象被称为“Shim”,它提供了一种替换依赖项行为的机制,这些依赖项未被(或无法)充分解耦以进行标准替换模拟。据我所知,TypeMock是目前唯一一个提供此类功能的主要模拟框架之一。
顺便说一下,如果您以前尝试过Moles,那么Fakes本质上是相同的东西,最终走出了微软研究部门,成为了一个实际的产品。