考虑以下情况,StubFoo是Foo的存根,我希望将其用于某些测试。
class Runner
def run
Foo = StubFoo
foo = Foo.new
# using Foo...
end
end
这会生成以下错误消息:Dynamic constant assignment
然而,在RSpec中,我可以做以下工作,这是有效的,并且非常合法:
it "should be an example" do
Foo = StubFoo
foo = Foo.new
foo.to_s.should == "I am stubbed!"
end
关于此的一些问题。
我在使用模拟框架之前这样做是为了纯粹想要了解Ruby中的模拟,存根等等。我听说动态语言更容易模拟/存根,并且互联网上有指南,其中简单的类重新分配如上所述。根据我的研究,在Ruby中,不可能在方法中声明常量,但我如上所述会感到困惑。
修改
是的,这开始变得更有意义了。我已经更新了run,现在正在使用const_set。
def run
old = Foo
self.class.const_set(:Foo, StubFoo)
foo = Foo.new
puts foo.to_s
self.class.const_set(:Foo, old)
foo = Foo.new
puts foo.to_s
end
这会产生一个警告,但这是什么/如何模拟框架在Ruby中工作?显然更优雅,功能齐全,但他们只是压制这个警告?
答案 0 :(得分:9)
您无法使用Constant = value
在方法定义中重新分配常量。但是,您可以使用const_set
重新分配它们。基本上它是为了阻止,但不是禁止动态不断的重新分配。
至于为什么它适用于it
:是的,it
是一种方法,但你没有定义,你正在调用它。重新分配发生在您作为参数传递给it
的块内。
Blocks继承创建它们的上下文的类型。这意味着在块内,self.class.name
将是您的测试类的名称。因此,当您在传递给it
方法的块内定义常量时,实际上是在测试类上定义了一个常量。
答案 1 :(得分:2)
因为在您的测试中,您没有定义方法,而是调用 it
。
Ruby检测到你在一个方法中定义一个常量,可以多次运行,因此警告。如果您不止一次调用该方法,则会收到warning: already initialized constant Foo
警告。这是因为它们应该是常数。 (例如,如果您定义了Foo = Time.now
会发生什么?)
答案 2 :(得分:0)
您收到错误是因为如果调用Runner#run
,Foo
将在调用者上意外更改。例如:
class C; end
def add_two_to(x) # this method is defined externally, and you cannot change it.
C = Class.new {} # error
x + 2
end
x = C.new
puts add_two_to(5)
y = C.new
x.is_a? y.class # would be false if the code get here
add_two_to
的来电者不希望重新定义C
。更有意义的是,上面代码的最后一行总是正确的。在你的第二个例子中,你的“方法”实际上不是一个方法,而是一个块。如果您运行该块两次(it
将不会执行),您将收到此错误:
warning: already initialized constant Q