重复具有不同参数的RSpec示例组

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中有更详细的示例代码.关于为什么这不太正常的任何线索?

Fre*_*ung 5

所以(道歉,如果我重复你已经知道的东西)但每次调用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)

当您调用itrspec时,创建一个Example对象并将其附加到self上的类实例变量(当前示例组).rspec还会在示例的元数据中粘贴当前示例组,向上走这个示例组树就可以获得示例的完整描述.

你的repetitively_describe方法调用describe,所以你调用example_group_block.call itemself确实是新创建的示例组.当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"]之前.