【问题标题】:Multiple assertions for single setup in RSpecRSpec 中单个设置的多个断言
【发布时间】:2014-10-27 09:16:18
【问题描述】:

我想优化一些速度较慢的规格。 此类规范的示例如下所示:

require 'rspec'


class HeavyComputation
  def compute_result
    sleep 1 # something compute heavy here
    "very big string"
  end

end



describe HeavyComputation, 'preferred style, but slow' do

  subject { described_class.new.compute_result }

  it { should include 'big' }
  it { should match 'string' }
  it { should match /very/ }
  # +50 others
end

这是非常可读的,我对它总体上很满意,除了每个额外的规范都会增加至少 1 秒的总运行时间。这是不太可接受的。

(请不要讨论HeavyComputation 类的优化,因为它超出了本问题的范围)。

所以我必须求助于这样的规范:

describe HeavyComputation, 'faster, but ugly' do
  subject { described_class.new.compute_result }

  it 'should have expected result overall' do
    should include 'big'
    should match 'string'
    should match /very/
    # +50 others
  end
end

这显然在性能方面要好得多,因为运行它的时间几乎是恒定的。 问题是故障很难追踪,而且阅读起来也不是很直观。

因此,理想情况下,我希望两者兼而有之。大致如下:

describe HeavyComputation, 'what I want ideally' do
  with_shared_setup_or_subject_or_something_similar_with do
    shared(:result) { described_class.new.compute_result  }
    subject         { result }

    it { should include 'big' }
    it { should match 'string' }
    it { should match /very/ }
    # +50 others
  end
end

但不幸的是,我什至看不到从哪里开始实施它。它存在多个潜在问题(是否应该在共享结果上调用挂钩)。

我想知道这个问题是否有现成的解决方案。 如果没有,最好的解决方法是什么?

【问题讨论】:

  • 除非您希望您的字符串在测试之间发生变化,否则为什么不在 before(:all) 块中设置一次?

标签: ruby-on-rails ruby rspec rspec2 rspec3


【解决方案1】:

您可以使用before(:context) 挂钩来实现此目的:

describe HeavyComputation, 'what I want ideally' do
  before(:context) { @result = described_class.new.compute_result }
  subject          { @result }

  it { should include 'big' }
  it { should match 'string' }
  it { should match /very/ }
  # +50 others
end

请注意,before(:context) 带有一些警告,但是:

警告:before(:context)

使用before(:context) 来加快速度非常诱人,但我们 建议您避免这种情况,因为也有许多陷阱 作为根本不起作用的东西。

上下文

before(:context) 在生成的示例中运行以提供组 块的上下文。

实例变量

before(:context) 中声明的实例变量在所有 群里的例子。这意味着每个示例都可以改变 共享对象的状态,导致排序依赖,可以 使失败的推理变得困难。

不支持的 rspec 构造

RSpec 有几个结构可以在每个示例之间重置状态 自动地。这些不适用于before(:context)

  • let 声明
  • subject 声明
  • 任何模拟、存根或测试双重声明

其他框架

模拟对象框架和数据库事务管理器(如 ActiveRecord)通常是围绕设置的想法设计的 在一个例子之前,运行那个例子,然后拆除。 这意味着可以(有时)在 before(:context),但在第一个真实示例出现之前就被拆除了 运行。

可以before(:context) 中创建数据库支持的模型对象 rspec-rails,但它不会为您包装在事务中,所以 你自己在after(:context) 块中清理。

(来自http://rubydoc.info/gems/rspec-core/RSpec/Core/Hooks:before

只要您了解您的 before(:context) 钩子超出了测试替身和数据库事务等事物的正常每个示例生命周期,并自己明确管理必要的设置和拆卸,您就可以了 - 但其他在您的代码库上工作的人可能不知道这些问题。

【讨论】:

【解决方案2】:

@Myron Marston 提供了一些灵感,所以我第一次尝试以或多或少可重用的方式实现它最终得到了以下用法(注意shared_subject):

describe HeavyComputation do
  shared_subject { described_class.new.compute_result }

  it { should include 'big' }
  it { should match 'string' }
  it { should match /very/ }
  # +50 others
end

这个想法是在第一个规范而不是在共享块中只渲染一次主题。 它几乎不需要更改任何内容(因为所有的钩子都会被执行)。

当然,shared_subject 采用共享状态及其所有怪癖。

但是每个新嵌套的context 都会创建一个新的共享主题,并且在一定程度上消除了状态泄漏的可能性。

更重要的是,为了处理状态泄漏,我们需要做的就是将shared_subject 替换回subject。然后你正在运行普通的 RSpec 示例。

我确定the implementation 有一些怪癖,但应该是一个不错的开始。

【讨论】:

    【解决方案3】:

    aggregate_failures,在 3.3 版中添加,将满足您的要求。它允许您在规范中拥有多个期望,而 RSpec 将运行每一个并报告所有失败,而不是在第一个失败时停止。

    问题在于,由于您必须将其放在单个规范中,因此您无法为每个期望命名。

    有块形式:

    it 'succeeds' do
      aggregate_failures "testing response" do
        expect(response.status).to eq(200)
        expect(response.body).to eq('{"msg":"success"}')
      end
    end
    

    还有一个元数据表格,适用于整个规范:

    it 'succeeds', :aggregate_failures do
      expect(response.status).to eq(200)
      expect(response.body).to eq('{"msg":"success"}')
    end
    

    见:https://www.relishapp.com/rspec/rspec-core/docs/expectation-framework-integration/aggregating-failures

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-04
      • 2017-09-18
      • 1970-01-01
      • 1970-01-01
      • 2011-02-22
      相关资源
      最近更新 更多