RSpec添加了一个“describe”方法做顶级命名空间。但是,不是简单地在任何类/模块之外定义方法,而是执行此操作:
# code from rspec-core/lib/rspec/core/dsl.rb
module RSpec
module Core
# Adds the `describe` method to the top-level namespace.
module DSL
def describe(*args, &example_group_block)
RSpec::Core::ExampleGroup.describe(*args, &example_group_block).register
end
end
end
end
extend RSpec::Core::DSL
Module.send(:include, RSpec::Core::DSL)
使用这种技术有什么好处,而不是简单地在任何模块和类之外定义描述? (据我所知,在rspec-core的其他任何地方都没有使用DSL模块。)
答案 0 :(得分:3)
我进行了此更改a few months ago,以便describe
不再添加到系统中的每个对象。如果您在顶层定义它:
def describe(*args)
end
...然后系统中的每个对象都有一个私有的describe
方法。 RSpec并不拥有系统中的每个对象,也不应该为每个对象添加describe
。我们只希望在两个范围内提供describe方法:
describe MyClass do
end
(在顶层,离开主要对象)
module MyModule
describe MyClass do
end
end
(关闭任何模块,因此您将描述嵌套在模块范围内)
将它放在一个模块中可以很容易地扩展到主对象(仅将其添加到该对象,而不是每个对象)并将其包含在Module
中(将其添加到所有模块中)。 / p>
答案 1 :(得分:1)
实际上,如果这就是代码中的所有内容,我真的不相信它会好得多 - 如果有的话。一个常见的争论是,您可以通过检查方法所有者来轻松检查RSpec是否负责在全局命名空间中添加此方法。不知何故,它从未觉得这是必要的,因为该方法的位置已经存储了该信息。
在任何范围之外定义方法将等同于在Object中定义私有实例方法:
class Object
private
def double(arg)
arg * 2
end
end
double(3) # OK
3.double(3) # Error: double is private
self.double(3) # Error: double is private
我认为私有性是一个有用的方面,因为它阻止了某些没有意义的方法调用,问题中显示的代码缺乏。
虽然在模块中定义方法是有好处的,但RSpec代码似乎没有使用它:使用module_function
,不仅保留了实例方法的私有性,而且你还得到一个公共类方法。这意味着如果你有一个同名的实例方法,你仍然可以通过使用类方法版本来引用模块定义的方法。
module_function
的常见示例是Kernel
模块,其中包含大多数类似函数的核心方法,如puts
(另一个是Math
)。如果您在重新定义puts
的班级中,如果需要,您仍然可以明确使用Kernel#puts
:
class LikeAnIO
def puts(string)
@output << string
end
def do_work
puts "foo" # inserts "foo" in @output
Kernel.puts "foo" # inserts "foo" in $stdout
end
end