10

有时,当我编写单元测试时,我需要在不initialize调用方法的情况下实例化一个类。例如,当构造函数实例化我将用存根替换的其他类时。例如:

class SomeClassThatIWillTest
  def initialize
    @client = GoogleAnalyticsClient.new
    @cache = SuperAdvancedCacheSystem.new
  end

  # ...
end

在测试中,我可能会用存根替换@client@cache,所以我宁愿构造函数永远不会被调用。有什么黑魔法可以帮助我解决这个问题吗?

4

3 回答 3

24

你当然可以。Class#new只不过是一种方便的方法,使您不必手动分配和初始化对象。它的实现大致是这样的:

class Class
  def new(*args, **kwargs, &blk)
    obj = allocate
    obj.send(:initialize, *args, **kwargs, &blk)
    obj
  end
end

您可以Class#allocate改为手动调用,而不是调用initialize.

于 2013-06-07T14:18:44.440 回答
4

您不应更改测试类的行为以对其进行单元测试。如果你的类在构造函数中有更多的动作,你每次都必须模仿它。您的测试将变得乏味维护。用双打替换对象(甚至类)。

也许您可以提供已经创建的对象作为构造函数的参数?它将允许您new在类上使用双打而不使用存根方法。

如果您使用 rspec,您可以:

GoogleAnalyticsClient.stub(new: double)
SuperAdvancedCacheSystem.stub(new: double)

定义你的双打以匹配预期的界面,瞧!不需要肮脏的技巧。

于 2013-06-07T11:12:10.350 回答
1

子类中的子类化SomeClassThatIWillTest和覆盖initialize呢?不涉及黑魔法;)

这样,您甚至可以调用super初始化程序(以测试其代码,如果它比您向我们展示的要多),然后再进行@client更改@cache

使用此方法的 MiniTest 规范示例:

describe MyTestClass do

  subject {
    Class.new(MyTestClass) {
      def initialize; end
    }
  }

  it "must do something" do
    subject.new.do_something.must_equal something
    # ...
  end

end
于 2013-06-07T11:14:09.710 回答