【发布时间】:2013-05-03 03:57:17
【问题描述】:
最近我一直在编写一些对象,其中一种方法的行为有时包括在某些条件下调用它自己的另一种方法。为了测试这一点,我一直在模拟对象调用自身的方法,因为它可以更容易地覆盖代码中的每个分支,而不会出现组合爆炸。这通常被认为是不好的做法。但这里有一个例子,它几乎完全反映了我现在正在处理的一段代码(用 CoffeeScript 编写,但这个哲学问题在我之前用其他语言也出现过)。
class UserDataFetcher
constructor: (@dataSource) ->
fetchUsernameAsync: ->
# ... snip ... Hits API and raises any errors
fetchUserCommentsAsync: ->
# ... snip ... Hits API and raises any errors
fetchUsernameAndUserCommentsAsync: ->
# ... snip ... Calls each of the above in order and handles errors specially
这很简单。 fetchUsernameAsync 和 fetchUserCommentsAsync 都与给定的 @dataSource 交互以获取数据。然后我们有fetchUsernameAndUserCommentsAsync,它本质上是一个组合方法,调用其他两个并以某种方式处理它们;在此示例中,它可能希望重新引发由 fetchUsernameAsync 引起的任何错误,但吞下由 fetchUserCommentsAsync 引发的任何错误。
测试前两种方法的行为很简单;我模拟了他们各自对@dataSource 的调用,并断言他们以正确的格式返回数据或允许引发任何错误。测试最后一种方法的行为是我进退两难的地方。
它仍然是一种简单的方法,具有最少的分支逻辑并通过传递消息来委派大部分工作。通常我会通过模拟它的“合作者”并断言某些消息正在传递并以适当的格式返回数据或正确失败来测试它的行为。但这里的不同之处在于它只有一个“合作者”this。我会嘲笑被测对象的一些方法,这被认为是不好的做法。
显然,解决这个问题的方法是将这个方法移动到不同的对象;它将是一个具有完全相同的方法和几乎完全相同的机制的对象,并且它将允许我在不违反“不要在被测对象上模拟方法”规则的情况下模拟其合作者。它可能会有一个荒谬的名字,比如UsernameAndCommentsFetcher,我会从一个小班变成两个小班,几乎是微观的班。小类很好,但是这种级别的粒度可能有点过头了,在这种情况下,它的存在只是为了满足“不要在被测对象上模拟方法”规则。
这是一个可靠的规则吗?还是在某些情况下,例如,可以合理地改变规则?
(注意:我意识到这与这个问题相似,但我认为这个例子足够不同,值得再看一下:As a "mockist" TDD practitioner, should I mock other methods in the same class as the method under test?)
【问题讨论】: