【问题标题】:Passing parameters to an rspec shared example将参数传递给 rspec 共享示例
【发布时间】:2017-01-30 07:25:13
【问题描述】:

我有一个返回结果集的过程,我想使用 rspec 测试其有效性。该过程将根据参数返回不同的结果,但有很多示例对所有参数都通用,因此我想创建一组可以针对所有参数运行的通用示例。

我知道首选的做法是使用 let 来构建结果。问题是每个过程需要一两分钟才能生成结果,我可能有 30 个示例。使用基于不同参数的所有排列,我运行了大约 500 个示例。如果我必须为每个示例重新构建结果,测试将运行超过一天。

因此,我在 before(:all) 块中构建结果并将其分配给类似这样的属性:

RSpec.describe 'Test Description' do
  attr_reader :result

  before(:all)
    @result = build_result({some_parameters})
  end

  context 'Some context' do
    it 'Looks lik a result' do 
      expect(result.something).to ...
    end

    it 'Feels lik a result' do 
      expect(result.something).to ...
    end
  end
end

也许有比使用属性更好的方法。我想做这样的事情:

RSpec.describe 'Test Description' do
  attr_reader :result

  before(:all)
    @result = build_result({some_parameters})
  end

  context 'Some context' do
    it_behaves_like "A result" result
  end
end

在此上下文中使用属性失败。有没有其他方法可以做到这一点?

【问题讨论】:

    标签: ruby rspec parameter-passing


    【解决方案1】:

    你可以使用let

    let(:result)     { build_result({some_parameters}) }
    

    这会创建一个实例变量,供您稍后在测试中使用。

    根据let 上的文档,

    当您必须分配变量而不是使用 before 块时 创建一个实例变量,使用 let。使用 let 变量惰性 仅在测试中第一次使用并被缓存时加载 直到完成特定的测试。

    不好

    describe '#type_id' do
      before { @resource = FactoryGirl.create :device }
      before { @type     = Type.find @resource.type_id }
    
      it 'sets the type_id field' do
        expect(@resource.type_id).to equal(@type.id)
      end
    end
    

    describe '#type_id' do
      let(:resource) { FactoryGirl.create :device }
      let(:type)     { Type.find resource.type_id }
    
      it 'sets the type_id field' do
        expect(resource.type_id).to equal(type.id)
      end
    end
    

    【讨论】:

    • 使用 let 的问题是它只在示例期间被缓存。如果我的测试中有 10 个“it”,那么它将计算 let 变量的值 10 次。就我而言,我必须为整个上下文构建一次结果,而不是为每个“它”构建结果。否则我的单元测试将运行超过一天,没有人会使用它们。
    • 您可以使用test-prof.evilmartians.io/#/let_it_be 来防止这种行为,并在示例之间缓存let
    【解决方案2】:

    您可以像这样将参数传递给共享示例:

    shared_examples_for "A result" do |argument|
     # some tests with argument
    end
    

    然后像这样传入my_argument

    it_behaves_like "A result", my_argument
    

    【讨论】:

    • 这是您在访问此页面时正在寻找的答案
    • 如果my_argument 是用let 定义的呢?它不会起作用。
    • 与@gamliela 相同的问题。用let 定义的变量可以传递给shared_examples 吗?
    • @gamliela,如果my_argument 是用let 定义的,则无需将其作为参数传递。您只需在shared_examples 中直接使用它。
    • @gamliela 您可以将工厂机器人参数作为参数传递,并在示例组中使用 let
    【解决方案3】:

    您可以将所有关于结果的断言分组到一个示例中。这样 let 只计算一次。

    RSpec.describe 'Test Description' do
      context 'for params x and y' do
        let(:expected_x) { 'x' }
        let(:expected_y) { 'y' }
    
        subject { build_result({x: 'x', y: 'y'}) }
    
        specify :aggregate_failures do
          expect(subject.x).to eq(expected_x)
          expect(subject.y).to eq(expected_y)
        end
      end
    end
    

    它确实违反了“一次测试,一次断言”的指导方针,但如果操作成本很高,我认为这是一种合理的做法。使用:aggregate_failures,您将获得每个断言的单独失败,因此您不会错过这一点。

    【讨论】:

    • 这不是我所希望的,但这是迄今为止最好的答案。
    【解决方案4】:

    如果你想将参数传递给 shared_example 规范文件,你应该使用类似

    it_behaves_like 'SHARED_EXAMPLE_NAME', { params1: param2, param2: param2 }
    

    您也可以不使用哈希封装传递参数。这取决于您将如何将该参数用于您的共享文件。 就我而言,我必须使用动态参数调用 API。

    RSpec.shared_examples 'SHARED_EXAMPLE_NAME' do |params = {}|
    

    仅供参考:您不能将 Factory 数据作为参数传递到 shared_example 规范文件中。您必须在 shared_example 规范文件中显式调用工厂。

    【讨论】:

      【解决方案5】:

      您可以使用块传递参数:

      feature 'Index page', js: true do
        context 'Filter' do
          before { visit(statistics_numbers_path) }
      
          it_behaves_like 'Page with select input', %i[create] do
            given!(:date) { Date.yesterday }
          end
        end
      end
      

      共享示例Page with select input 将接收参数features 包含[:create]

      shared_examples 'Page with select input' do |features|
        context 'Table with results', js: true do
          given!(:existed_number) { 'number_1' }
          given!(:created_number) { 'number_2' }
          given!(:search_regexp) { 'numb' }
      
          scenario 'You can select existed numbers', skip: !features.include?(:select) do
            number_selector.choose_option(existed_number)
      
            click_on 'Find'
      
            expect(page).to have_css('.table tr td', text: existed_number)
          end
      
          scenario 'You can create tags with new names', skip: !features.include?(:create) do
            number_selector.create_tag(created_number)
      
            click_on 'Find'
      
            expect(page).to have_css('.table tr td', text: created_number)
          end
      
          scenario 'You can search by regular expression', skip: !features.include?(:regexp) do
            number_selector.choose_all_occurrences_of(search_regexp)
      
            click_on 'Find'
      
            expect(page).to have_css('.table tr td', text: existed_number)
            expect(page).to have_css('.table tr td', text: created_number)
          end
        end
      end
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-01-13
        相关资源
        最近更新 更多