mock 和 stub 之间的区别非常简单 - mock 可以使您的测试失败,而 stub 不能。这就是全部。此外,您可以将存根视为提供值的东西。如今,fake 只是两者的总称(稍后会详细介绍)。
示例
让我们考虑一个案例,您必须构建一个通过通信协议发送包的服务(具体细节无关紧要)。您只需提供带有包代码的服务,剩下的就交给它了。鉴于下面的 sn-p,您能否确定哪个依赖项是 stub 以及潜在单元测试中的哪个 mock?
public class DistributionService
{
public double SendPackage(string packageCode)
{
var contents = this.packageService.GetPackageContents(packageCode);
if (contents == null)
{
throw new InvalidOperationException(
"Attempt to send non-exisiting package");
}
var package = this.packageBuilder.Build(contents);
this.packageDistributor.Send(package);
}
}
很容易看出packageBuilder 只是提供价值,它不可能使任何测试失败。这是一个存根。即使看起来更模糊,packageService 也是存根。它提供了一个值(从存根的角度来看,我们对这个值所做的事情是无关紧要的)。当然,稍后我们将使用该值来测试是否抛出异常,但它仍然在我们的控制范围内(例如,我们准确地告诉 stub 做什么而忘记它——它应该对测试没有进一步的影响)。
packageDistributor 会有所不同。即使它提供任何价值,也不会被消耗。然而,对Send 的调用似乎是我们实现中非常重要的一部分,我们很可能希望验证它是否被调用。
此时我们应该得出结论,packageDistributor 是一个mock。我们将有一个专门的单元测试断言 Send 方法被调用,如果由于某些原因它没有被调用 - 我们想知道这一点,因为它是整个过程的重要部分。其他依赖项都是存根,因为它们所做的只是为其他可能更相关的代码片段提供值。
快速浏览 TDD
存根是存根,在朴素实现中也可以用常量值替换:
var contents = "Important package";
var package = "<package>Important package</package>";
this.packageDistributor.Send(package);
这本质上是模拟框架对存根所做的 - 指示它们返回可配置/显式值。 老式的、手动的存根经常这样做 - 返回常量值。
显然,这样的代码没有多大意义,但任何做过 TDD 的人肯定在类开发的早期阶段看到过很多这样的幼稚实现。 TDD 产生的迭代开发通常有助于识别角色类的依赖关系。
如今的存根、模拟和赝品
在本文开头我提到 fake 只是一个通用术语。鉴于 mock 也可以用作存根(尤其是在涉及现代模拟框架时),为避免混淆,将此类对象称为假对象是个好主意。如今,您可以看到这种趋势正在增长 - 原始 mock - stub 区别正在慢慢成为过去,并且使用了更通用的名称。例如:
- FakeItEasy 使用 假
- NSubstitute 使用 substitute
- Moq 使用 mock(名称很旧,但无论是 stub 还是 mock 都没有明显的区别)
参考资料,进一步阅读