模拟被测对象的方法

时间:2013-05-03 03:57:18

标签: mocking tdd bdd

最近我一直在写一些对象,其中一种方法的行为有时包括在某些条件下调用另一种方法。为了测试这个,我一直在模拟对象调用的方法,因为它可以更容易地覆盖代码中的每个分支而不会发生组合爆炸。这通常被认为是不好的做法。但是这里有一个例子几乎完全反映了我现在正在处理的一段代码(用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

这非常简单。 fetchUsernameAsyncfetchUserCommentsAsync分别与给定的@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?

1 个答案:

答案 0 :(得分: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测试不需要涵盖之前规范已涵盖的所有路径。