1

继续另一个关于测试的类似问题(见这里)。我将使用一个类似的例子(伪代码)

class LinkDisplayer
    method constructor(LinkStorage)
    method displayLatestLinksByCategory(number_of_them)

class LinkStorage
    method saveLink(Link)
    method retrieveLatestLinksByCategory(category, number_of_them)

class Link
    method getUrl()
    method getDescription()
    method getCategory()

所以 linkDisplayer 使用 LinkStorage 来获取链接。我要测试的行为是“shouldDisplayLatestLinks”。在我的测试中,我是否需要模拟 LinkStorage,并让它返回具有模拟 getUrl() 等行为的模拟 Link 对象?

测试“叶子”类很容易,但我仍然很难找到测试其他类的方法。

4

1 回答 1

3

简短的回答:

您应该模拟/存根不受 SUT 直接控制的任何内容。只测试您的 SUT 的行为,并且永远不要编写尝试确认超出该范围的行为的测试(即测试模拟/存根)。


长答案:

很难只见树木不见森林。当您是编写所有代码的人时,您有时会发现很难避免测试过于细化的实现细节。

你有正确的想法想要测试行为,当你开始考虑你的测试时,危险信号就消失了。这正是 TDD 的全部意义所在,因为它有助于暴露设计缺陷。请记住,只测试 SUT 的行为。其他一切都应该在您的单元测试(模拟)的控制之下,否则它就不是单元测试。

将自己置于 LinkDisplayer 类的消费者的位置,并问自己“我将如何在生产代码中使用它?” 您可能只是调用该方法并期望它有效。您肯定不会调用数据库来确保它确实检索了正确数量的元素,或者它们是按类别排序的吗?那么为什么要为此编写测试。

call 的最终结果应该是displayLatestLinksByCategory什么?

根据您的样本,我看到了该问题的两个可能答案,以及您应该根据这些答案采取哪些措施:

  1. 使用特定方法从数据库中获取一些东西,并将其显示在某个地方。
    • 如果这是该方法的作用,那么您应该测试的只有这两件事。主要是使用正确的参数在数据访问组件上调用正确的方法;并且返回的数据到达了它需要去的地方。
    • 应该测试的是返回数据的形状***,因为这只会测试您的测试已经完全控制的行为。这就像测试您在测试中刚刚设置为 true 的变量实际上是否为 true。
    • 使用特定方法从数据库中获取一些东西并返回它
    • 在这一点上,红旗应该到处都是。如果这是此方法的唯一好处,那么您必须问自己为什么不直接调用数据访问方法?作为此类的使用者,您已经可以控制正在使用哪个数据访问组件(您提供了它),所以为什么要有中间人。

*可以测试您的 SUT 如何响应特定形状的数据。如果返回 null 或集合为空,您可能希望抛出异常。如果返回的数量超过 N,您可能希望从列表中删除额外的值,但您永远不想测试超出 SUT 行为的内容。因为你可以直接控制从你的模拟返回的数据是如何形成的,所以测试可能会导致最糟糕的测试;一个不测试任何东西但仍然通过的测试。

于 2009-08-12T20:16:15.510 回答