有些时候,当我编写单元测试时,我需要实例化一个没有调用initialize
方法的类。例如,当构造函数实例化其他类时,无论如何我将替换为存根。例如:
class SomeClassThatIWillTest
def initialize
@client = GoogleAnalyticsClient.new
@cache = SuperAdvancedCacheSystem.new
end
# ...
end
在测试中,我可能会用存根替换@client
和@cache
,所以我宁愿从不调用构造函数。是否有任何黑魔法可以帮助我解决这个问题?
答案 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以供使用,例如在你的测试中。