pat*_*pat 5 ruby rspec metaprogramming
我正在尝试保持我的规格清洁和干燥,但我对API的测试除了正在测试的API的版本之外是相同的.我可以简单地使用这样的东西重复规范:
%w( v1 v2 ).each do |version|
describe "Query #{version} API" do
it "responds with JSON"
# make the call using the version
end
end
end
Run Code Online (Sandbox Code Playgroud)
但我想要一些更清洁的东西,所以我写了这个方法:
module RepetitivelyDescribe
def repetitively_describe(*args, &example_group_block)
options = args.extract_options!
options.delete(:for).each do |item|
item_args = args.collect(&:dup) + [options.dup]
item_args[0] << " [#{item}]"
describe(*item_args) do
example_group_block.call item
end
end
end
end
RSpec::Core::ExampleGroup.extend RepetitivelyDescribe
Run Code Online (Sandbox Code Playgroud)
然后我的测试看起来更像这样:
repetitively_describe "Query API", :for => %( v1 v2 ) do |version|
it "responds with JSON"
# make the call using the version
end
end
Run Code Online (Sandbox Code Playgroud)
我意识到这有点迂腐,但它的压痕程度要低一些,如果我要打这么多话,我想让它变得更干净.
但是,当然,它并不像我想的那样工作.对describe
我内部的调用repetitively_describe
没有记录到RSpec输出(使用文档格式输出时),尽管其中的示例会重复并按预期使用版本块参数.本质上,该上下文级别丢失(describe
块的外部和内部的repetitively_describe
块保持不变).
如果需要,可以在gist中有更详细的示例代码.关于为什么这不太正常的任何线索?
所以(道歉,如果我重复你已经知道的东西)但每次调用describe/context时,rspec都会创建一个新类,它是当前示例组类的子类(最终是它的子类RSpec::Core::ExampleGroup
),然后用于module_eval
评估阻止在该类的上下文中.如果我跑
describe "foo" do
puts "#{self}; #{self.superclass}"
describe "behaviour 1" do
puts "#{self}; #{self.superclass}"
context "with x" do
puts "#{self}; #{self.superclass}"
end
end
end
Run Code Online (Sandbox Code Playgroud)
那么输出就是
#<Class:0x007fb772bfbc70>; RSpec::Core::ExampleGroup
#<Class:0x007fb772bfb180>; #<Class:0x007fb772bfbc70>
#<Class:0x007fb772bfa5f0>; #<Class:0x007fb772bfb180>
Run Code Online (Sandbox Code Playgroud)
当您调用it
rspec时,创建一个Example
对象并将其附加到self上的类实例变量(当前示例组).rspec还会在示例的元数据中粘贴当前示例组,向上走这个示例组树就可以获得示例的完整描述.
你的repetitively_describe
方法调用describe
,所以你调用example_group_block.call item
self确实是新创建的示例组.当proc被评估时,它当然会记住self
调用它时的值是什么,所以你的调用it
是针对重复性描述时最新的示例组(通过在代码中调用一些调用来检查self的值,可以轻松验证) ).类似地,对describe的调用将示例组添加为外部示例组的子项,而不是由其创建的子项repetitively_describe
.
你当然需要做的就是example_group_block
保持正确的自我价值.
module RepetitivelyDescribe
def repetitively_describe(*args, &example_group_block)
options = args.extract_options!
options.delete(:for).each do |item|
item_args = args.collect(&:dup) + [options.dup]
item_args[0] << " [#{item}]"
describe(*item_args) do
class_exec(item, &example_group_block)
end
end
end
end
Run Code Online (Sandbox Code Playgroud)
随着这种变化
describe('foo') do
repetitively_describe "Query API", :for => %w( v1 v2 ) do |version|
it "responds with JSON"
end
end.descendant_filtered_examples.collect(&:full_description)
Run Code Online (Sandbox Code Playgroud)
输出["foo Query API [v1] responds with JSON", "foo Query API [v2] responds with JSON"]
而不是变化["foo responds with JSON", "foo responds with JSON"]
之前.