行为测试与状态测试的区别

5
我知道这个问题在某种程度上是宗教战争的一部分,但我有以下情况: 我有一个对象Responder,它根据不同的事件调用对象Updater的方法。最近我将测试分为两部分:对于Updater方法本身进行基于状态的测试,对于调用它的Responder进行基于行为的测试。也就是说,在Responder测试中,我模拟了Updater,只是为了确保它被调用。
那么,在Responder测试中,我是否仍然应该测试应该更新的对象的状态,而不是模拟Updater?我喜欢我的做法,因为它需要更少的设置,并且似乎更好地隔离了测试。但是,这似乎将Responder的实现和预期行为与Updater联系起来,这样会不会太脆弱?这只是一个简化的例子。

什么样的测试可以令您满意地确认代码是否可行?单靠集成测试就足够吗? - Robert Harvey
我认为一个将所有部分综合起来的集成测试会增加我的信心。然而,我也想知道人们是否认为这是在单元级别进行测试的有效方法。 - Matt H
2个回答

10

如果我正确理解了你的问题,你确实需要至少两个级别的测试:

  1. 单元测试,其中你尝试只测试一个类并模拟所有依赖关系(因此在您的情况下,需要在这里模拟Updater)。这些测试有助于您开发代码(特别是如果您正在使用TDD),确保类的行为符合设计要求,甚至记录此类应如何运行。几乎每个类都应该有单元测试。但是,正如您所注意到的,即使您拥有100%的测试覆盖率,也不能保证程序工作或启动!

  2. 验收、集成和端到端测试-这些测试涵盖整个应用程序或大型模块,并测试所有内容是否正常协同工作。通常在这个级别不使用模拟(但可能会对整个模块/网络服务进行存根,具体取决于上下文)。这些测试不必测试每个实现细节(也不应该),因为单元测试会完成这项工作。它们确保一切正确连接和协同工作。在您的情况下,您不需要在此处模拟Updater。

因此,总结一下,我认为您真的需要这两种测试来正确测试您的应用程序。


“几乎每个类都应该有单元测试。”为什么?“单元测试,你试图仅测试一个类并模拟所有依赖项。”所有依赖项?你确定吗? - a better oliver
@abetteroliver 理论上来说,所有的静态辅助函数都是需要测试的。但实际上,有时候你可能会忽略一些静态辅助函数等。总是有一种诱惑让这些代码在生产环境中运行,但这可能会使得测试所有代码路径变得棘手,因此不被鼓励。 - Grzenio
如果一个类的实例创建了一个列表,那么你肯定不会对它进行模拟。也不能这样做。如果你从一个类中提取出一些代码并将其放入一个单独的类中,为什么要模拟该依赖项?该代码以前一直在类中,而不是被模拟的。这些都使得“模拟所有依赖项”成为一个有趣的建议。此外,还有比模拟更多种类的测试替身。最后但并非最不重要的是,单元测试不限于单个类,如果你所说的“单元测试,其中你试图仅测试一个类”。 - a better oliver

0

我猜这取决于你试图测试什么/如何测试。

如果在Updater类中没有任何依赖项,那么除了在调用要测试的方法后测试其状态之外,没有其他方法。如果存在某些依赖关系,则可以测试它的行为(调用其他类上的其他方法)

对于Responder类,也可以采用相反的方式。您可以测试它的状态,也可以像刚才一样测试它的行为。测试其状态时,您可能会使用实际的Updater实现而不是模拟它,或者使用一个最小化执行的存根代替它。

当大多数测试行为并模拟很多东西时,就更有必要进行验收(回归/集成/端到端)测试,以支持您的单元测试,就像Grzenio所提到的那样。(他似乎是一位“模拟”(行为)测试者)

当主要进行基于状态的测试时,您将使用更多的实际实现及其交互,并且支持它们的需求较不相关。但这绝不是完全放弃它们的方法,而是因为您已经在测试中具有一些回归/集成测试。

我认为您不应将测试分开得太多,因为这可能会使事情更容易落入漏洞。

免责声明:我不是tdd大师,也不倾向于其中任何一种。关于传统模式和行为驱动模式的好信息可以在网络上找到。Martin Fowler在这个主题上有一个很好的起点:Mocks Aren't Stubs

我目前正在阅读《Growing Object Oriented Software, Guided by Tests》并偏向于模拟测试(mockist)的一方。但我认为双方都存在一个小灰色区域。


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