3

最近我一直在写一些对象,其中一个方法的行为有时包括在某些条件下调用另一个它自己的方法。为了测试这一点,我一直在模拟对象调用自身的方法,因为它可以更容易地覆盖代码中的每个分支,而不会出现组合爆炸。这通常被认为是不好的做法。但这里有一个例子,它几乎完全反映了我现在正在处理的一段代码(用 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它本质上是一个组合方法,它调用另外两个并以某种方式处理它们;在这个例子中,它可能想要重新引发任何fetchUsernameAsyncfetchUserCommentsAsync.

测试前两种方法的行为很简单;我模拟了他们各自的调用,@dataSource并断言他们以正确的格式返回数据或允许引发任何错误。测试最后一种方法的行为是我进退两难的地方。

它仍然是一种简单的方法,具有最少的分支逻辑并通过传递消息来委派大部分工作。通常我会通过模拟它的“合作者”并断言某些消息正在传递并以适当的格式返回数据或正确失败来测试它的行为。但这里的不同之处在于它只有一个“合作者”,this. 我会嘲笑被测对象的一些方法,这被认为是不好的做法。

显然,解决这个问题的方法是将此方法移至不同的对象;它将是一个具有完全相同的方法和几乎完全相同的机制的对象,并且它将允许我在不违反“不要在被测对象上模拟方法”规则的情况下模拟其合作者。它可能会有一个荒谬的名字,比如UsernameAndCommentsFetcher,我会从一个小班变成两个小班,几乎是微观的班。小类是好的,但是这种级别的粒度可能有点过头了,在这种情况下,它的存在只是为了满足“不要在被测对象上模拟方法”规则。

这是一个可靠的规则,还是有像这样的情况,可以合理地改变规则?

(注意:我意识到这与这个问题相似,但我认为这个例子足够不同,值得再看一看:作为一个“mockist”TDD 从业者,我应该在与被测方法相同的类中模拟其他方法吗?

4

1 回答 1

1

编程中很少有规则是 100% 可靠的。

但是在这种情况下,您能否将 fetchUsernameAsync 和 fetchUserCommentsAsync 的模拟设置代码提取到设置方法中,然后在 fetchUsernameAndUserCommentsAsync 中调用这两个方法?例如(在类似 RSpec 的语法中):

specify "fetchUsernameAsync" do
  setup_username_mock
  expect(...).to eq ...
end

specify "fetchCommentsAsync" do
  setup_comments_mock
  expect(...).to eq ...
end

specify "fetchUsernameAndCommentsAsync" do
  setup_username_mock
  setup_comments_mock
  expect(...).to eq ...
end

fetchUsernameAndCommentsAsync 测试无需涵盖先前规范已涵盖的所有路径。

于 2013-05-04T12:28:52.843 回答