5

我试图保持我的规范干净和干燥,但是我对一个 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

我意识到这有点迂腐,但它的缩进层次少了一层,如果我要经常打这个电话,我想让它更干净。

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

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

4

1 回答 1

6

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

那么输出是

#<Class:0x007fb772bfbc70>; RSpec::Core::ExampleGroup
#<Class:0x007fb772bfb180>; #<Class:0x007fb772bfbc70>
#<Class:0x007fb772bfa5f0>; #<Class:0x007fb772bfb180>

当您调用itrspec 创建一个Example对象并将其附加到 self 上的类实例变量(当前示例组)。rspec 还将当前示例组粘贴在示例的元数据中,沿着示例组树向上走就是为您提供示例的完整描述。

您的repetitively_describe方法调用describe,因此在您调用example_group_block.call itemself 时确实是新创建的示例组。当 proc 被评估时,它当然会记住self它被调用时的值是什么,所以你的调用it是对当前的示例组进行的,当重复_describe 时(通过在整个代码中撒一些调用来检查 self 的值,很容易验证)。类似地,对 describe 的调用将示例组添加为外部示例组的子组,而不是由 . 创建的组repetitively_describe

你当然需要做的是调用example_group_block保持 self 的正确值。

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"]而不是["foo responds with JSON", "foo responds with JSON"]更改之前。

于 2012-06-02T10:35:22.067 回答