我很惊讶这个问题已经存在了这么长时间,但是还没有人根据
Roy Osherove的《单元测试的艺术》提供答案。
在“3.1引入存根”中,定义存根为:
一个存根是系统中现有依赖项(或协作者)的可控替代品。通过使用存根,您可以在不直接处理依赖关系的情况下测试代码。
并且将存根和模拟之间的区别定义为:
关于模拟和存根的主要区别是,模拟与存根非常相似,但您对模拟对象进行断言,而不对存根进行断言。
Fake只是用于存根和模拟的名称。例如,当您不关心存根和模拟之间的区别时。
Osherove所区分的存根和模拟的方式意味着,用作测试的任何类都可以是存根或模拟。它对于特定测试来说是什么取决于您如何编写测试中的检查。
- 当你的测试检查被测试类中的值或者任何其他地方的值,但不包括伪造对象时,伪造对象被用作存根。它只提供被测试类使用的值,直接通过调用返回的值或者间接地通过调用引起副作用 (在某个状态下)。
- 当你的测试检查伪造对象的值时,它被用作模拟对象。
一个将FakeX类用作存根的测试示例:
const pleaseReturn5 = 5;
var fake = new FakeX(pleaseReturn5);
var cut = new ClassUnderTest(fake);
cut.SquareIt;
Assert.AreEqual(25, cut.SomeProperty);
"
fake
实例被用作存根,因为Assert
根本不使用fake
。
测试类X被用作模拟的示例:
"
const pleaseReturn5 = 5;
var fake = new FakeX(pleaseReturn5);
var cut = new ClassUnderTest(fake);
cut.SquareIt;
Assert.AreEqual(25, fake.SomeProperty);
在这种情况下,
Assert
检查了
fake
上的一个值,使得该fake成为了一个mock。
当然,这些示例非常虚构,但我认为这种区分非常有价值。它让你意识到你如何测试你的东西以及你的测试依赖关系在哪里。
我同意Osherove的观点:
从纯可维护性的角度来看,使用mock在我的测试中比不使用mock更麻烦。这是我的经验,但我总是学到新东西。
尽量避免针对fake进行断言,因为这会使你的测试高度依赖于一个根本不是被测试对象的类的实现。这意味着,如果
ClassUsedAsMock
的实现发生了改变,那么
ActualClassUnderTest
的测试可能会开始出现问题。这对我来说很糟糕。最好只有在
ActualClassUnderTest
发生改变时才会导致
ActualClassUnderTest
的测试失败。
我意识到针对虚假情况写出断言是一种常见的做法,特别是当你是一位模拟测试驱动开发者时。我想我坚定地站在经典派阵营中(参见
Martin Fowler的“Mocks aren't Stubs”),并像Osherove一样尽可能避免交互测试(只能通过针对虚假情况进行断言来完成)。
如果您想了解有关为什么应该避免使用此处定义的模拟对象的有趣阅读,请搜索“fowler mockist classicist”。您会发现大量不同的观点。