是否可以在不调用initialize的情况下实例化Ruby类?

时间:2013-06-07 11:01:54

标签: ruby unit-testing

有些时候,当我编写单元测试时,我需要实例化一个没有调用initialize方法的类。例如,当构造函数实例化其他类时,无论如何我将替换为存根。例如:

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

  # ...
end

在测试中,我可能会用存根替换@client@cache,所以我宁愿从不调用构造函数。是否有任何黑魔法可以帮助我解决这个问题?

4 个答案:

答案 0 :(得分:18)

当然可以。 Class#new只不过是一种方便的方法,可以让您不必手动分配和初始化对象。它的实现看起来大致如下:

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

您可以手动拨打Class#allocate,而不是拨打initialize

答案 1 :(得分:4)

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

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

如果您使用的是rspec,则可以:

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

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

答案 2 :(得分:2)

如何在子类中继承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

答案 3 :(得分:0)

不,你不能(缺少亚经典,指出tessi的评论)。但不是这样:

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

考虑在getter中延迟加载@client和@cache,并添加setter以供使用,例如在你的测试中。