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

时间:2012-06-02 09:48:13

标签: 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

但是我想要一些更清洁的东西,所以我写了这个方法:

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

然后我的测试看起来更像是这样:

repetitively_describe "Query API", :for => %( v1 v2 ) do |version|
  it "responds with JSON"
    # make the call using the version 
  end
end

我意识到这有点迂腐,但它的缩进程度要低一些,如果我打算多打这个电话,我想让它变得更干净。

但当然,它并不像我想的那样有效。我的describe中对repetitively_describe的调用未记录到RSpec输出(使用文档格式输出时),尽管其中的示例会重复并按预期使用版本块参数。从本质上讲,该上下文级别会丢失(保留describe块外部和内部的repetitively_describe块。

如果需要,a gist中有更详细的示例代码。关于为什么这不正常工作的任何线索?

1 个答案:

答案 0 :(得分:5)

所以(道歉,如果我重复你已经知道的东西),但每次调用describe / context时,rspec都会创建一个新类,它是当前示例组类的子类(最终是{{1}的子类})然后使用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

当您调用#<Class:0x007fb772bfbc70>; RSpec::Core::ExampleGroup #<Class:0x007fb772bfb180>; #<Class:0x007fb772bfbc70> #<Class:0x007fb772bfa5f0>; #<Class:0x007fb772bfb180> 时,rspec创建一个it对象并将其附加到self上的类实例变量(当前示例组)。 rspec还会在示例的元数据中粘贴当前示例组,向上走这个示例组树就可以获得示例的完整描述。

您的Example方法会调用repetitively_describe,因此,当您调用describe self时,确实是新创建的示例组。当proc被评估时,它当然会记住调用它时example_group_block.call item的值是什么,所以你对self的调用是针对重复描述时最新的示例组(通过洒一些可以轻松验证)要求在整个代码中检查self的值。类似地,对describe的调用会将示例组添加为外部示例组的子项,而不是it创建的子项。

您当然需要做的是致电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

在更改之前输出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) 而不是["foo Query API [v1] responds with JSON", "foo Query API [v2] responds with JSON"]