7

我有一些较慢的规格需要优化。此类规范的示例如下所示:

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

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

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

4

3 回答 3

4

您可以使用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),但在第一个真实示例运行之前就被拆除了。

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

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

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

于 2014-09-03T02:46:38.357 回答
1

@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_subjectsubject. 然后你正在运行普通的 RSpec 示例。

我确信这个实现有一些怪癖,但应该是一个很好的开始。

于 2014-09-23T02:43:50.133 回答
1

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

于 2016-05-20T00:16:42.623 回答