RSpec 3.1 发布了!

Myron Marston

2014 年 9 月 4 日

RSpec 3.1 刚刚发布!考虑到我们对 语义化版本控制 的承诺,对于任何已经使用 RSpec 3.0 的人来说,这应该是一个微不足道的升级。但是,如果我们确实引入了任何回归,请告诉我们,我们会尽快发布一个包含修复的补丁版本。

RSpec 仍然是一个社区驱动的项目,拥有来自世界各地的贡献者。此版本包含来自 47 位不同贡献者的 190 个 pull 请求的 647 次提交。

感谢所有帮助使这次发布成为现实的人!

值得注意的变化

核心:回溯过滤更改

在 RSpec 2.x 和 RSpec 3.0 中,默认的回溯过滤配置从打印的回溯中排除了 gems 中的代码行。我们 收到了一些反馈,认为这没有帮助,并且已经从默认的回溯过滤器模式中删除了 /gems/。在 RSpec 3.1 中,来自 gems 的代码行将包含在回溯中,但来自 RSpec 本身的代码行将继续被排除。当然,如果你仍然想要排除 gems,你可以轻松地自己添加这个模式。

# spec/spec_helper.rb
RSpec.configure do |config|
  config.backtrace_exclusion_patterns << /gems/
end

此外,我们添加了一个新的 API,可以轻松地过滤掉一个或多个特定的 gems。

# spec/spec_helper.rb
RSpec.configure do |config|
  config.filter_gems_from_backtrace "rack", "rake"
end

核心:新的 --exclude-pattern 选项

RSpec 3.1 有一个新的 --exclude-pattern 选项,它是 --pattern 的反义词。这允许你排除特定的文件,例如,你可以加载和运行所有 spec 文件,除了来自特定目录的文件。

rspec --pattern "spec/**/*_spec.rb" --exclude-pattern "spec/acceptance/**/*_spec.rb"

rake 任务定义 API 也支持这个选项,因此可以轻松地定义运行所有 specs 除了来自一个目录的 specs 的任务。

# Rakefile
require 'rspec/core/rake_task'

desc "Run all but the acceptance specs"
RSpec::Core::RakeTask.new(:all_but_acceptance) do |t|
  t.exclude_pattern = "spec/acceptance/**/*_spec.rb"
end

感谢 John Gesimondo 建议并 实现 此新功能!

核心:现在可以独立使用,无需配置

RSpec 在 RSpec 2.0 中被拆分为单独的 gems(核心、期望和模拟),这允许用户 混合和匹配 RSpec 的各个部分 与其他测试库,如 Minitest。Tom Stuart 指出,仅仅使用 rspec-core 并不像预期的那样容易。具体来说,在 RSpec 3.0 及之前版本中,如果你没有明确配置 rspec-core 不要使用 rspec-expectations 和 rspec-mocks,它会假设它们可用,尝试加载它们,并在它们不可用时报错。

在 RSpec 3.1 中,我们已经纠正了这个问题。rspec-expectations 和 rspec-mocks 仍然会默认使用(如果可用),但如果它们不可用,并且你没有进行任何配置,rspec-core 将正常工作。

感谢 Sam Phippen 实现 了此改进。

核心:警告标志不再在生成的文件中默认设置为 true

在 RSpec 3.0 中,我们将 --warnings 放入生成的 .rspec 文件中。我们这样做是为了鼓励 gem 作者使他们的 gems 无警告(因为发出警告的 gems 会阻止用户使用 Ruby 的警告模式)。但是,rails 生态系统通常没有努力实现无警告代码,在新的 rails 项目中,这导致了大量令人困惑的警告。

在 RSpec 3.1 中,我们从社区反馈中吸取了教训,并从生成的 .rspec 文件中删除了 --warnings。在非 rails 项目中,rspec --init 将在 spec_helper.rb 中推荐设置的注释部分包含 config.warnings = true

感谢 Andrew Hooker 做了这个更改

期望:新的 have_attributes 匹配器

这个新的匹配器使根据对象的属性来匹配对象变得容易。

Person = Struct.new(:name, :age)
person = Person.new("Coen", 3)
expect(person).to have_attributes(name: "Coen", age: 3)

它也被别名为 an_object_having_attributes,这在组合匹配器表达式中特别有用。

people = [Person.new("Coen", 3), Person.new("Daphne", 2)]
expect(people).to match([
  an_object_having_attributes(name: "Coen",   age: 3),
  an_object_having_attributes(name: "Daphne", age: 2)
])

它也可以用作消息期望的实参匹配器。

expect(email_gateway).to receive(:send_receipt).with(
  an_object_having_attributes(email: "foo@example.com")
)

感谢 Adam Farhi 实现 了这个新的匹配器。

期望:块匹配器现在可以在复合表达式中使用

RSpec 3.0 获得了使用 复合匹配器表达式 的能力。但是,它不适用于块期望,因为我们有一些内部更改需要进行,以确保块只执行一次,正如预期的那样。我们在 3.1 中解决了这个问题,允许像这样的表达式。

x = y = 0
expect {
  x += 1
  y += 2
}.to change { x }.to(1).and change { y }.to(2)

期望:新的 define_negated_matcher API

这个新的 API 提供了一种方法来定义现有匹配器的否定版本。

# define a negated form of `include`...
RSpec::Matchers.define_negated_matcher :exclude, :include

# ...which allows you to write:
expect(odd_numbers).to exclude(14)

# ...rather than:
expect(odd_numbers).not_to include(14)

它本身并没有给你带来什么帮助。但是,在处理组合或复合匹配器表达式时,它非常有用。

adults = Town.find("Springfield").adults
marge  = Character.find("Marge")
bart   = Character.find("Bart")

expect(adults).to include(marge).and exclude(bart)

感谢 Adam Farhi 帮助 实现 了此功能。

期望:自定义匹配器链式修饰符现在包含在生成的描述中

自定义匹配器 DSL 允许你使用 chain 来定义一个流畅的接口。

RSpec::Matchers.define :be_smaller_than do |max|
  chain :and_bigger_than do |min|
    @min = min
  end

  match do |actual|
    actual < max && actual > @min
  end
end

# usage:
expect(10).to be_smaller_than(20).and_bigger_than(5)

在 RSpec 2.x 和 3.0 中,链式部分没有包含在失败消息中。

Failure/Error: expect(5).to be_smaller_than(10).and_bigger_than(7)
  expected 5 to be smaller than 10

RSpec 3.1 可以将链式部分包含在失败消息中。

Failure/Error: expect(5).to be_smaller_than(10).and_bigger_than(7)
  expected 5 to be smaller than 10 and bigger than 7

…但只有在你使用配置选项启用此行为的情况下。

RSpec.configure do |config|
  config.expect_with :rspec do |expectations|
    expectations.include_chain_clauses_in_custom_matcher_descriptions = true
  end
end

此配置选项默认设置为 false,以保证向后兼容。我们计划在 RSpec 4 中始终启用它。

感谢 Dan Oved 实现 了此改进!

模拟:新的 *_spy 方法

RSpec 2.14 添加了对将测试替身用作间谍的支持,这允许你在事后设置一个关于消息是否接收到的期望。

spy = double(:foo => nil)
# do something with spy
expect(spy).to have_received(:foo)

请注意,我们在这里对 foo 进行了存根。这是必要的,因为替身默认情况下是严格的,这意味着当它们收到意外的消息时会抛出错误。不幸的是,这会强迫你进行一些重复,因为你必须两次声明该方法(在之前进行存根,并在之后期望它被接收)。

你可以使用 as_null_object 来解决这个问题,它使替身变得“松散”而不是严格,允许它接收任何消息。

spy = double.as_null_object
# do something with spy
expect(spy).to have_received(:foo)

这种模式非常有用,因此在 RSpec 3.1 中,我们添加了新的方法来声明间谍。

spy(...)          # equivalent to double(...).as_null_object
instance_spy(...) # equivalent to instance_double(...).as_null_object
class_spy(...)    # equivalent to class_double(...).as_null_object
object_spy(...)   # equivalent to object_double(...).as_null_object

感谢 Justin Searls 提出了这个问题,并感谢 Sam Phippen 实现了 这些新方法。

模拟:新的 and_wrap_original API

这个新的 API 允许你轻松地装饰特定对象上的特定现有方法,只在当前示例的持续时间内。原始方法作为第一个参数传递给你的块(在实际方法调用参数之前)。

allow(api_client).to receive(:fetch_users).and_wrap_original do |original_method, *args|
  original_method.call(*args).first(10) # truncate the response to the first 10 users
end

感谢 Jon Rowe 实现了 此功能。

Rails:Rails 4.2 支持

rspec-rails 3.1 将正式支持 Rails 4.2。Aaron Kromer 一直在努力使 RSpec 3.1 为 Rails 4.2 做好准备。这包括一个由 Abdelkader Boudih 提供 的 ActiveJob 生成器。

Rails:生成的 rails_helper.rb 默认不再自动加载 spec/support 文件

Aaron 还 做了一个小改动,用于生成 rails_helper.rb。以前,它包含一些代码,这些代码会自动加载 spec/support 下的所有文件。该代码仍然存在,但现在被注释掉了。我们发现,手动需要所需的 support 文件,而不是始终加载所有文件,这有助于防止加载时膨胀。

当然,如果你喜欢以前方法的方便性,这是一个合理的权衡,你可以轻松地取消注释这部分代码。

统计

rspec-core

rspec-expectations

rspec-mocks

rspec-rails

rspec-support

文档

API 文档

Cucumber 功能

发布说明

rspec-core-3.1.0

完整变更日志

增强功能

错误修复

rspec-expectations-3.1.0

完整变更日志

增强功能

错误修复

rspec-mocks-3.1.0

完整变更日志

增强功能

Bugfixes

rspec-rails-3.1.0

完整变更日志

增强功能

错误修复