【问题标题】:Repeating RSpec example groups with different arguments使用不同的参数重复 RSpec 示例组
【发布时间】:2012-06-02 09:48:12
【问题描述】:

我正在努力保持我的规范干净和干燥,但我对一个 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

我意识到这有点学究气,但缩进少了一级,如果我要经常打这个电话,我希望它更干净。

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

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

【问题讨论】:

标签: ruby rspec metaprogramming


【解决方案1】:

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

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

您的repetitively_describe 方法调用describe,因此在您调用example_group_block.call item 时,self 确实是新创建的示例组。当 proc 被评估时,它当然会记住 self 被调用时的值,因此您对 it 的调用是对当前的示例组进行的整个代码中的自我价值)。类似地,对 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"]

【讨论】:

  • 完美,谢谢 - 我也很欣赏所有的细节!
  • 实际上,不是很完美:repetitively_describe 块中定义的方法不适用于内部示例。有什么想法吗?
  • 啊,是的,在这种情况下,您需要 class_exec 而不是 instance_exec - 更改在块内执行 def blah; end 时定义方法的位置
  • class_exec 成功了 - 但是我产生的变量(版本)在方法中不可用。选择 let 调用而不是方法 - 不太合适,但它会做。谢谢:)
  • 使用 define_method 可能也有帮助
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-13
  • 1970-01-01
  • 2017-01-30
  • 1970-01-01
  • 1970-01-01
  • 2023-03-03
相关资源
最近更新 更多