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

时间:2023-01-15 21:55:14

Some times when I write unit tests I need to instantiate a class without the initialize method being invoked. For instance when the constructor instantiates other classes that I will replace with stubs anyway. For instance:

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

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

  # ...
end

In a test I will probably replace both @client and @cache with stubs, so I'd rather the constructor was never invoked. Is there any black magic that can help me out with that?

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

4 个解决方案

#1


2  

What about subclassing SomeClassThatIWillTest and overwriting initialize in the subclass? No black magic involved ;)

如何在子类中继承SomeClassThatIWillTest并覆盖初始化?没有黑魔法;)

This way you could even call the super initializer (to test its code, if its more than you showed us), and then alter @client and @cache afterwards.

这样你甚至可以调用超级初始化程序(测试它的代码,如果它比你给我们的更多),然后改变@client和@cache。

Example of a MiniTest spec using this method:

使用此方法的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

#2


16  

Sure you can. Class#new is nothing more than a convenience method that saves you from having to allocate and initialize an object manually. Its implementation looks roughly like this:

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

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

You can just call Class#allocate manually instead, and not call initialize.

您只需手动调用Class#allocate,而不是调用initialize。

#3


4  

You should not change behavior of tested class in order to unit test it. If your class will have more actions in constructor you will have to mimic it every time. Your test will get tedious to maintain. Replace objects (or even classes) with doubles.

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

Maybe you could provide already created objects as an arguments to the constructor? It would allow you to use doubles without stubbing new method on classes.

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

If you are using rspec, you can:

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

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

Define your doubles to match expected interface, and voila! No dirty tricks needed.

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

#4


0  

No you cannot (short of subclassic, as point out tessi's comment). But instead of this:

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

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

Consider lazy loading @client and @cache in getters, and adding setters for use e.g. in your tests.

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

#1


2  

What about subclassing SomeClassThatIWillTest and overwriting initialize in the subclass? No black magic involved ;)

如何在子类中继承SomeClassThatIWillTest并覆盖初始化?没有黑魔法;)

This way you could even call the super initializer (to test its code, if its more than you showed us), and then alter @client and @cache afterwards.

这样你甚至可以调用超级初始化程序(测试它的代码,如果它比你给我们的更多),然后改变@client和@cache。

Example of a MiniTest spec using this method:

使用此方法的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

#2


16  

Sure you can. Class#new is nothing more than a convenience method that saves you from having to allocate and initialize an object manually. Its implementation looks roughly like this:

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

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

You can just call Class#allocate manually instead, and not call initialize.

您只需手动调用Class#allocate,而不是调用initialize。

#3


4  

You should not change behavior of tested class in order to unit test it. If your class will have more actions in constructor you will have to mimic it every time. Your test will get tedious to maintain. Replace objects (or even classes) with doubles.

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

Maybe you could provide already created objects as an arguments to the constructor? It would allow you to use doubles without stubbing new method on classes.

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

If you are using rspec, you can:

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

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

Define your doubles to match expected interface, and voila! No dirty tricks needed.

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

#4


0  

No you cannot (short of subclassic, as point out tessi's comment). But instead of this:

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

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

Consider lazy loading @client and @cache in getters, and adding setters for use e.g. in your tests.

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