RSpec 3 中的显著变化
Myron Marston
2014 年 5 月 21 日更新:现在有 日语翻译。
RSpec 3.0.0 RC1 已经发布几天了,3.0.0 正式版也即将发布。我们过去 6 个月一直在使用测试版,很高兴能与大家分享。以下是新功能
所有 gem
删除对 Ruby 1.8.6 和 1.9.1 的支持
这些版本的 Ruby 已经很久以前就停止维护了,RSpec 3 不支持它们。
改进 Ruby 2.x 支持
最近发布的 RSpec 2.x 版本(即在 Ruby 2.0 发布后发布的版本)已正式支持 Ruby 2,但 RSpec 3 的支持得到了极大改善。我们现在提供对使用 Ruby 2 新功能的支持,例如关键字参数和预置模块。
新的 rspec-support gem
rspec-support 是一个新的 gem,我们正在使用它来处理多个 rspec-(core|expectations|mocks|rails) 共享的通用代码。它目前不包含任何供最终用户或扩展库作者使用的公共 API,但我们可能会在将来公开部分 API。
如果您从 github 中获取最新版本的 RSpec,您还需要从 github 中获取 rspec-support。
强大、经过良好测试的升级过程
RSpec 3 中的每一个重大更改在 2.99 中都有相应的弃用警告。在测试版期间,我们进行了多次升级,以确保此过程尽可能顺利。我们已经整理了逐步升级说明.
升级过程还突出显示了 RSpec 的新弃用系统,该系统高度可配置(允许您将弃用输出到文件或将所有弃用转换为错误),并旨在最大程度地减少重复的弃用输出。
改进的文档
我们投入了大量精力更新所有 gem 的 API 文档。它们目前托管在 rubydoc.info
…但我们目前正在更新 rspec.info 以自行托管它们。
虽然文档仍在开发中(坦率地说,永远不会停止开发),但我们已经确保明确声明所有公共 API 作为 SemVer 兼容性的一部分。我们绝对致力于在所有 3.x 版本中维护所有公共 API。另一方面,私有 API 被标记为私有 API,因为我们希望在任何 3.x 版本中保留随意更改它们的灵活性。
请勿使用我们声明为私有的 API。如果您发现自己需要现有公共 API 未满足的 API,请询问。我们很乐意为您的需求公开私有 API 或添加新的 API 来满足您的用例。
gem 现在已签名
我们已经开始对我们的 gem 版本进行签名。虽然当前的 gem 签名系统远非理想,并且 更好的解决方案 正在开发中,但它总比没有好。我们已经将我们的公共证书放在 GitHub 上。
有关当前 gem 签名系统的更多详细信息,请参阅 使用签名 Ruby gem 的实用指南.
零猴子补丁模式
RSpec 现在可以在没有任何猴子补丁的情况下使用。这方面的大部分基础工作都是在最近的 2.x 版本中完成的,这些版本在 rspec-expectations 和 rspec-mocks 中添加了新的 expect
基于语法。我们在 RSpec 3 中完成了剩下的工作,并为剩下的猴子补丁提供了替代方案。
为了方便起见,您可以使用一个选项禁用所有猴子补丁
# spec/spec_helper.rb
RSpec.configure do |c|
c.disable_monkey_patching!
end
感谢 Alexey Fedorov 实现 此配置选项。
更多信息
rspec-core
钩子范围的新名称::example
和 :context
RSpec 2.x 有三个不同的钩子范围
describe MyClass do
before(:each) { } # runs before each example in this group
before(:all) { } # runs once before the first example in this group
end
# spec/spec_helper.rb
RSpec.configure do |c|
c.before(:each) { } # runs before each example in the entire test suite
c.before(:all) { } # runs before the first example of each top-level group
c.before(:suite) { } # runs once after all spec files have been loaded, before the first spec runs
end
有时,用户对 :each
和 :all
的含义感到困惑,尤其是 :all
在配置块中使用时会令人困惑
# spec/spec_helper.rb
RSpec.configure do |c|
c.before(:all) { }
end
在这种情况下,术语 :all
暗示此钩子将在套件中所有示例之前运行一次——但这正是 :suite
的作用。
在 RSpec 3 中,:each
和 :all
有别名,使它们的范围更加明确::example
是 :each
的别名,:context
是 :all
的别名。请注意,:each
和 :all
未被弃用,我们也没有计划这样做。
感谢 John Feminella 实现 这项功能。
更多信息
DSL 方法将示例作为参数传递
RSpec::Core::Example
提供对示例的所有详细信息的访问:其描述、位置、元数据、执行结果等。在 RSpec 2.x 中,示例通过 example
方法公开,该方法可以从任何钩子或单个示例中访问
describe MyClass do
before(:each) { puts example.metadata }
end
在 RSpec 3 中,我们已删除 example
方法。相反,示例实例被传递给所有示例范围的 DSL 方法作为显式参数
describe MyClass do
before(:example) { |ex| puts ex.metadata }
let(:example_description) { |ex| ex.description }
it 'accesses the example' do |ex|
# use ex
end
end
感谢 David Chelimsky 想出了这个主意并 实现 它!
更多信息
新的 expose_dsl_globally
配置选项以禁用 rspec-core 猴子补丁
RSpec 2.x 对 main
和 Module
进行猴子补丁,以提供顶级方法,例如 describe
、shared_examples_for
和 shared_context
shared_examples_for "something" do
end
module MyGem
describe SomeClass do
it_behaves_like "something"
end
end
在 RSpec 3 中,这些方法现在也适用于 RSpec
模块(除了仍然可以作为猴子补丁使用之外)
RSpec.shared_examples_for "something" do
end
module MyGem
RSpec.describe SomeClass do
it_behaves_like "something"
end
end
您可以通过将新的 expose_dsl_globally
配置选项设置为 false
来完全删除 rspec-core 的猴子补丁(这将导致上面的第一个示例引发 NoMethodError
)
# spec/spec_helper.rb
RSpec.configure do |config|
config.expose_dsl_globally = false
end
更多信息
使用 alias_example_group_to
定义示例组别名
在 RSpec 2.x 中,我们提供了一个 API,允许您定义具有附加元数据的 example
别名。例如,这在内部用于定义 fit
作为 it
的别名,并带有 :focus => true
元数据
# spec/spec_helper.rb
RSpec.configure do |config|
config.alias_example_to :fit, :focus => true
end
在 RSpec 3 中,我们已将此功能扩展到示例组
# spec/spec_helper.rb
RSpec.configure do |config|
config.alias_example_group_to :describe_model, :type => :model
end
您可以在使用 rspec-rails 的项目中使用此示例,并使用 describe_model User
而不是 describe User, :type => :model
。
感谢 Michi Huber 实现 这项功能。
更多信息
新的示例组别名:xdescribe
、xcontext
、fdescribe
、fcontext
除了包含定义示例组别名的 API 之外,我们还包含了几个额外的内置别名(除了 describe
和 context
之外)
xdescribe
/xcontext
与xit
类似,可用于暂时跳过示例组。fdescribe
/fcontext
与fit
类似,可用于暂时将:focus => true
元数据添加到示例组,以便您可以通过config.filter_run :focus
轻松过滤到关注的示例和组。
更多信息
对 pending
语义的更改(以及 skip
的引入)
现在运行挂起的示例以检查它们是否确实通过。如果挂起块失败,则它将像以前一样标记为挂起。但是,如果它成功,则会导致失败。这有助于确保挂起的示例有效,并且在实现其描述的行为时会及时处理它们。
为了支持旧的“从不运行”行为,添加了 skip
方法和元数据。以下所有示例都不会运行
describe Post do
skip 'not implemented yet' do
end
it 'does something', :skip => true do
end
it 'does something', :skip => 'reason explanation' do
end
it 'does something else' do
skip
end
it 'does something else' do
skip 'reason explanation'
end
end
通过此更改,在示例中向 pending
传递块不再有意义,因此已删除该行为。
感谢 Xavier Shay 实现 这项功能。
更多信息
用于单行语句的新 API:is_expected
RSpec 多年来一直具有单行语句语法
describe Post do
it { should allow_mass_assignment_of(:title) }
end
在这种情况下,should
不是可以被移除的猴子补丁 should
,可以通过将 rspec-expectations 配置为仅支持 :expect
语法来移除它。它没有猴子补丁 Object
与 should
相伴而来的包袱,并且始终可用,无论您的语法配置如何。
一些用户对这种 should
如何与 expect
语法相关联以及您是否可以继续使用它表示困惑。它将在 RSpec 3 中继续可用(再次,无论您的语法配置如何),但我们还添加了一个更符合 expect
语法的备用 API
describe Post do
it { is_expected.to allow_mass_assignment_of(:title) }
end
is_expected
定义非常简单,定义为 expect(subject)
,并且还通过 is_expected.not_to matcher
支持否定期望。
更多信息
示例组可以单独排序
RSpec 2.8 将随机排序引入 RSpec,这对发现规范套件中无意中的排序依赖关系非常有用。在 RSpec 3 中,它不再是一个全有或全无的功能。您可以通过使用适当的元数据标记它们来控制单个示例组的排序方式
describe MyClass, :order => :defined do
# examples in this group will always run in defined order,
# regardless of any other ordering configuration.
end
describe MyClass, :order => :random do
# examples in this group will always run in random order,
# regardless of any other ordering configuration.
end
这对于从定义的排序迁移到随机排序特别有用,因为它允许您在针对特定组选择加入此功能时,逐一处理排序依赖关系,而不是必须一次性解决所有问题。
作为此的一部分,我们还将 --order default
重命名为 --order defined
,因为我们意识到“默认”是一个高度重叠的术语。
感谢 Andy Lindeman 和 Sam Phippen 帮助 实现 此功能。
更多信息
新的排序策略 API
在 RSpec 3 中,我们对排序策略 API 进行了彻底的修改。过去曾经是 三个 不同 方法 现在成为了一种方法:register_ordering
。使用它来定义一个名为排序策略
# spec/spec_helper.rb
RSpec.configure do |config|
config.register_ordering(:description_length) do |list|
list.sort_by { |item| item.description.length }
end
end
describe MyClass, :order => :description_length do
# ...
end
或者,您可以使用它来定义全局排序
# spec/spec_helper.rb
RSpec.configure do |config|
config.register_ordering(:global) do |list|
# sort them alphabetically
list.sort_by { |item| item.description }
end
end
:global
排序用于对顶层示例组进行排序,以及对没有 :order
元数据的示例组进行排序。
更多信息
rspec --init
改进
rspec
命令长期以来一直提供 --init
选项来设置项目的骨架。在 RSpec 3 中,它生成的 文件已得到很大改进,以提供更好的开箱即用体验,并提供一个具有更多推荐设置的 spec/spec_helper.rb
文件。
请注意,生成的 文件中已注释掉不打算成为未来默认值的推荐设置,因此打开此文件并接受您想要的推荐设置是个好主意。
更多信息
新的 --dry-run
CLI 选项
此选项将打印规范套件的格式化输出,而不会运行任何示例或钩子。它作为一种查看规范套件的文档输出而无需等待规范运行或担心其通过/失败状态的方法特别有用。
感谢 Thomas Stratmann 贡献 此功能!
更多信息
格式化程序 API 更改
添加了一个全新的格式化程序 API,它更加灵活。
- 仅订阅您关心的事件。
- 方法接收通知对象而不是特定参数,因此可以在向后兼容的方式添加新的通知数据。
- 在通知对象上公开辅助方法,因此不再有效地需要从
BaseTextFormatter
继承。
新的格式化程序看起来像这样
class CustomFormatter
RSpec::Core::Formatters.register self, :example_started
def initialize(output)
@output = output
end
def example_started(notification)
@output << "example: " << notification.example.description
end
end
为了继续支持旧的 2.x 格式化程序 API,提供了 rspec-legacy_formatters gem。
感谢 Jon Rowe 负责此项工作。
更多信息
断言配置更改
虽然大多数用户使用 rspec-expectations,但使用其他东西也很容易,并且 RSpec 2.x 通过配置选项使最常见的备选方案轻松可用
# spec/spec_helper.rb
RSpec.configure do |config|
config.expect_with :stdlib
# or, to use both:
config.expect_with :stdlib, :rspec
end
但是,围绕 :stdlib
存在一些混淆。在 Ruby 1.8 上,标准库断言模块是 Test::Unit::Assertions
。在 1.9+ 上,它是 Minitest::Assertions
的一个薄包装器(通常您最好只使用它)。同时,还有一个 test-unit gem 定义了 Test::Unit::Assertions
(它不是 minitest 的包装器)和一个 minitest gem。
对于 RSpec 3,我们已删除 expect_with :stdlib
,而是选择了明确的 :test_unit
和 :minitest
选项
# spec/spec_helper.rb
RSpec.configure do |config|
# for test-unit:
config.expect_with :test_unit
# for minitest:
config.expect_with :minitest
end
感谢 Aaron Kromer 为 实现此功能。
更多信息
定义派生元数据
RSpec 的元数据系统非常灵活,允许您以多种方式对测试套件进行切片和切块。有一个新的配置 API 允许您定义派生元数据。例如,要使用 :js => true
自动标记 spec/acceptance/js
中的所有示例组
# spec/spec_helper.rb
RSpec.configure do |config|
config.define_derived_metadata(:file_path => %r{/spec/acceptance/js/}) do |metadata|
metadata[:js] = true
end
end
更多信息
移除
一些不再是 RSpec 核心的东西要么被完全删除,要么被提取到外部 gem 中
- Textmate 格式化程序已移至 Textmate 包。在 rspec-core 中为一个特定的文本编辑器提供格式化程序实际上没有意义。
- 已删除 RCov 集成。它从未更新以与 1.9+ 一起使用,现在我们建议使用 simplecov。
- 已删除
--debug
CLI 选项。现在有许多不同的调试器选项,您可以使用--require
(或-r
)选项从命令行激活它们。例如,要使用 byebug,请在命令行传递-rbyebug
。 - 我们已删除
--line-number
CLI 选项。它一开始就具有模棱两可的语义(--line-number 43
将过滤到每个加载的规范文件中定义的靠近第 43 行的示例,但没有理由每个文件中的第 43 行会相关),并且重复了更简洁的path/to/spec.rb:43
形式。 its
已被提取到新的 rspec-its gem 中,Peter Alfvin 友情提供维护。- Autotest 集成已提取到新的 rspec-autotest gem 中(需要维护者:有志愿者吗?)。
rspec-expectations
在没有显式启用 should
语法的情况下使用它已被弃用
在 RSpec 2.11 中,我们开始通过 引入新的基于 expect 的语法 来消除 RSpec 中的猴子修补。在 RSpec 3 中,我们保留了 should
语法,它默认可用,但如果您在没有显式启用它的情况下使用它,您将收到弃用警告。这将为在 RSpec 4 中将其默认禁用(或可能提取到单独的 gem 中)铺平道路,同时最大限度地减少通过旧教程来到 RSpec 的新手的混淆。
我们认为 expect
语法现在是 RSpec 的“主要”语法,但如果您更喜欢旧的基于 should
的语法,请随意继续使用它:我们没有计划永远杀死它。
感谢 Sam Phippen 为 实现 此功能。
更多信息
复合匹配器表达式
在 RSpec 3 中,您可以使用 and
或 or
将多个匹配器链接在一起
# these two expectations...
expect(alphabet).to start_with("a")
expect(alphabet).to end_with("z")
# ...can be combined into one expression:
expect(alphabet).to start_with("a").and end_with("z")
# You can also use `or`:
expect(stoplight.color).to eq("red").or eq("green").or eq("yellow")
它们是 &
和 |
运算符的别名
expect(alphabet).to start_with("a") & end_with("z")
expect(stoplight.color).to eq("red") | eq("green") | eq("yellow")
感谢 Eloy Espinaco 为 建议并实现 此功能,以及 Adam Farhi 为 使用 &
和 |
运算符扩展它。
更多信息
可组合匹配器
RSpec 3 允许您通过将匹配器作为参数传递给其他匹配器来表达详细的意图
s = "food"
expect { s = "barn" }.to change { s }.
from( a_string_matching(/foo/) ).
to( a_string_matching(/bar/) )
expect { |probe|
"food".tap(&probe)
}.to yield_with_args( a_string_starting_with("f") )
为了提高代码表达式和失败消息的可读性,大多数匹配器都有别名,这些别名在这些表达式中作为参数传递时可以正确读取。
更多信息
- RSpec 3 中的新增功能:可组合匹配器
- rspec-expectations #280 - 原始讨论
- rspec-expectations #393 - 实现
- API 文档(包括匹配器别名列表)
- Relish 文档
match
匹配器可用于数据结构
在 RSpec 3 之前,match
匹配器存在于使用 #match
方法执行字符串/正则表达式匹配。
expect("food").to match("foo")
expect("food").to match(/foo/)
在 RSpec 3 中,它还支持匹配任意嵌套的数组/哈希数据结构。预期值可以使用任何级别的嵌套匹配器来表达
hash = {
:a => {
:b => ["foo", 5],
:c => { :d => 2.05 }
}
}
expect(hash).to match(
:a => {
:b => a_collection_containing_exactly(
an_instance_of(Fixnum),
a_string_starting_with("f")
),
:c => { :d => (a_value < 3) }
}
)
更多信息
新的 all
匹配器
此匹配器允许您指定集合中的所有项目都为真。传递一个匹配器作为参数
expect([1, 3, 5]).to all( be_odd )
感谢 Adam Farhi 为 贡献 此功能!
更多信息
新的 output
匹配器
此匹配器可用于指定块写入 stdout 或 stderr
expect { print "foo" }.to output("foo").to_stdout
expect { print "foo" }.to output(/fo/).to_stdout
expect { warn "bar" }.to output(/bar/).to_stderr
感谢 Matthias Günther 为 建议 此功能(以及推动它)以及 Luca Pette 为 完成此功能。
更多信息
新的 be_between
匹配器
RSpec 2 为实现 between?
的对象提供了 be_between
匹配器,使用动态谓词支持。在 RSpec 3 中,我们正在获得一个更好的 be_between
匹配器,它在几个方面更好
- 失败消息好多了——它不会告诉你
between?(1, 10)
返回了 false,而是会告诉你expected 11 to be between 1 and 10
。 - 它适用于实现比较运算符(例如
<
、<=
、>
、>=
)但未实现between?
的对象。 - 它提供
inclusive
和exclusive
模式。
# like `Comparable#between?`, it is inclusive by default
expect(10).to be_between(5, 10)
# ...but you can make it exclusive:
expect(10).not_to be_between(5, 10).exclusive
# ...or explicitly label it inclusive:
expect(10).to be_between(5, 10).inclusive
感谢 Erik Michaels-Ober 为 贡献 此功能,以及 Pedro Gimenez 为 改进 它!
更多信息
布尔匹配器已重命名
RSpec 2 有一对匹配器(be_true
和 be_false
),它们反映了 Ruby 的条件语义:be_true
将对除 nil
或 false
之外的任何值都通过,而 be_false
将对 nil
或 false
通过。
在 RSpec 3 中,我们已将它们重命名为 be_truthy
和 be_falsey
(或者 be_falsy
,如果你更喜欢这个拼写),以使它们的语义更明确,并减少与 be true
/be false
(它们与 be_true
/be_false
读起来一样,但只有在给出确切的 true
/false
值时才会通过)的混淆。
感谢 Sam Phippen 为 实现 此功能。
更多信息
match_array
匹配器现在可作为 contain_exactly
使用
RSpec 长期以来都有一个匹配器,它允许您匹配两个数组的内容,同时忽略任何排序差异。最初,这是使用 =~
运算符与旧的 should
语法一起使用的
[2, 1, 3].should =~ [1, 2, 3]
后来,当我们 添加 expect
语法 时,我们决定不将运算符匹配器转发到新语法,而是将匹配器称为 match_array
expect([2, 1, 3]).to match_array([1, 2, 3])
match_array
是我们当时能想到的最佳名称,但我们对它并不太满意:“match”是一个不精确的术语,而匹配器旨在用于除数组之外的其他类型的集合。我们在 RSpec 3 中为它想出了一个更好的名称
expect([2, 1, 3]).to contain_exactly(1, 2, 3)
请注意,match_array
没有被弃用。这两种方法的行为完全相同,只是 contain_exactly
接受单独展开的项目,而 match_array
接受单个数组参数。
更多信息
集合基数匹配器已提取到 rspec-collection_matchers
gem 中
集合基数匹配器——have(x).items
、have_at_least(y).items
和 have_at_most(z).items
——是 RSpec 中最“神奇”和令人困惑的部分之一。它们已被 提取 到 rspec-collection-matchers gem 中,Hugo Baraúna 慷慨地自愿维护它。
一般替代方法是对集合的大小设置期望
expect(list).to have(3).items
# ...can be written as:
expect(list.size).to eq(3)
expect(list).to have_at_least(3).items
# ...can be written as:
expect(list.size).to be >= 3
expect(list).to have_at_most(3).items
# ...can be written as:
expect(list.size).to be <= 3
改进的 Minitest 集成
在 RSpec 2.x 中,rspec-expectations 会 自动包含自身 在 MiniTest::Unit::TestCase
或 Test::Unit::TestCase
中,以便您只需加载它就可以从 Minitest 或 Test::Unit 使用 rspec-expectations。
在 RSpec 3 中,我们以几种方式更新了此集成
- 与 Minitest 4(或更低版本)或 Test::Unit 的集成不再是自动的。如果您在这样的环境中使用 rspec-expectations,您需要自己
include RSpec::Matchers
。 - 现在提供了对 Minitest 5 的改进集成,但您必须通过
require 'rspec/expectations/minitest_integration'
显式加载它
更多信息
匹配器协议的更改
如上所述,在 RSpec 3 中,我们不再认为 should
是 rspec-expectations 的主要语法。我们已更新匹配器协议以反映这一点
failure_message_for_should
现在是failure_message
。failure_message_for_should_not
现在是failure_message_when_negated
。match_for_should
(自定义匹配器 DSL 中的match
的别名)已删除,没有替换。(只需使用match
)。- 自定义匹配器 DSL 中的
match_for_should_not
现在是match_when_negated
。
此外,我们添加了 supports_block_expectations?
作为匹配器协议的新可选部分。这用于在用户在块期望表达式中错误地使用值匹配器时为他们提供清晰的错误。例如,在更改之前,在使用 be_nil
之类的匹配器时将块传递给 expect
可能导致误报
expect { foo.bar }.not_to be_nil
# ...is equivalent to:
block = lambda { foo.bar }
expect(block).not_to be_nil
# ...but the block is not nil (even though `foo.bar` might return nil),
# so the expectation will pass even though the user probably meant:
expect(foo.bar).not_to be_nil
请注意,supports_block_expectations?
是协议的可选部分。对于不打算在块期望表达式中使用的匹配器,您不需要定义它。
更多信息
- rspec-expectations #270 - 原始讨论
- rspec-expectations #373 - 实现
- rspec-expectations #530 - 关于
supports_block_expectations?
的原始讨论 - rspec-expectations #530 -
supports_block_expectations?
的实现
rspec-mocks
在没有显式启用它的情况下使用猴子修补语法已被弃用
与 rspec-expectations 一样,我们一直在将 rspec-mocks 朝着零猴子修补语法方向发展。这最初是在 2.14 中引入的。在 RSpec 3 中,如果您在没有显式启用它(就像 rspec-expectations 的新语法一样)的情况下使用原始语法(例如 obj.stub
、obj.should_receive
等),您将收到弃用警告。
感谢 Sam Phippen 为 实现 此功能。
receive_messages
和 receive_message_chain
用于新语法
原始的猴子补丁语法有一些新语法(在 2.14 中发布)缺乏的功能。我们在 RSpec 3 中通过两个新的 API 解决了这个问题:receive_messages
和 receive_message_chain
。
# old syntax:
object.stub(:foo => 1, :bar => 2)
# new syntax:
allow(object).to receive_messages(:foo => 1, :bar => 2)
# old syntax:
object.stub_chain(:foo, :bar, :bazz).and_return(3)
# new syntax:
allow(object).to receive_message_chain(:foo, :bar, :bazz).and_return(3)
这些新 API 的一个好处是它们也与 expect
一起工作,而在旧语法中没有 stub(hash)
或 stub_chain
的消息期望等效项。
感谢 Jon Rowe 和 Sam Phippen 为此功能的实现。
更多信息
receive_messages
的文档receive_message_chain
的文档- rspec-mocks #368 -
receive_messages
的讨论 - rspec-mocks #399 -
receive_messages
的实现 - rspec-mocks #464 -
receive_message_chain
的讨论 - rspec-mocks #467 -
receive_message_chain
的实现
删除了 double
的 mock
和 stub
别名
历史上,rspec-mocks 提供了三种创建测试双重的方法:mock
、stub
和 double
。在 RSpec 3 中,我们去掉了 mock
和 stub
,只保留了 double
,并构建了更多使用 double
命名法的功能(例如验证双重 - 参见下文)。
当然,虽然 RSpec 3 不再提供 double
的 mock
和 stub
别名,但如果你想继续使用它们,可以很容易地在你自己定义这些别名。
# spec/spec_helper.rb
module DoubleAliases
def mock(*args, &block)
double(*args, &block)
end
alias stub mock
end
RSpec.configure do |config|
config.include DoubleAliases
end
感谢 Sam Phippen 为此功能的 实现。
更多信息
验证双重
添加了一种新的双重类型,它确保你只对实际存在的 method 进行 stub 或 mock,并且传递的参数符合声明的 method 签名。instance_double
、class_double
和 object_double
双重将在不满足这些条件时抛出异常。如果该类尚未加载(通常在孤立地运行单元测试时),则不会抛出任何异常。
这是一种细微的行为,但非常强大,因为它允许孤立单元测试的速度与集成测试(或类型系统)更接近的信心。很少有理由不使用这些新的更强大的双重类型。
感谢 Xavier Shay 为此功能的理念和实现。
更多信息
部分双重验证配置选项
可以 全局启用部分双重的验证双重行为。(部分双重是指当你模拟或 stub 一个现有对象时:expect(MyClass).to receive(:some_message)
。)
# spec/spec_helper.rb
RSpec.configure do |config|
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
end
我们建议你为所有新代码启用此选项。
范围更改
rspec-mocks 的操作是针对每个测试的生命周期设计的。这在 RSpec 2 中有记录,但在运行时并不总是明确强制执行,有时我们会收到用户在尝试在每个测试的生命周期之外使用 rspec-mocks 的功能时遇到的错误报告。
在 RSpec 3 中,我们已经加强了这一点,并且此生命周期在运行时明确强制执行。
- 不支持从
before(:context)
钩子(或在没有当前示例的任何其他上下文中)使用 rspec-mocks 功能。 - 测试双重只能用于一个示例。如果你试图在生成测试双重的示例之外使用它(例如,通过意外地将其分配给类属性,然后在后面的示例中使用它),你会得到明确的错误。
我们还提供了一个新的 API,它允许你在任意位置(例如 before(:context)
钩子)创建临时范围。
describe MyWebCrawler do
before(:context) do
RSpec::Mocks.with_temporary_scope do
allow(MyWebCrawler).to receive(:crawl_depth_limit).and_return(5)
@crawl_results = MyWebCrawler.perform_crawl_on("http://some-host.com/")
end # verification and resets happen when the block completes
end
# ...
end
感谢 Sam Phippen 帮助 实现 这些更改,以及 Sebastian Skałacki 为新的 with_temporary_scope
功能提出建议。
更多信息
any_instance
实现块会 yield 接收方
当为 method stub 提供一个实现块时,根据对象的 state 进行一些计算可能很有用。不幸的是,在 RSpec 2 中使用 any_instance
时,没有简单的办法做到这一点。在 RSpec 3 中,接收方将作为 any_instance
实现块的第一个参数 yield,这使得这变得很容易。
allow_any_instance_of(Employee).to receive(:salary) do |employee, currency|
usd_amount = 50_000 + (10_000 * employee.years_worked)
currency.from_usd(usd_amount)
end
employee = Employee.find(23)
salary = employee.salary(Currency.find(:CAD))
感谢 Sam Phippen 为此功能的 实现。
更多信息
rspec-rails
文件类型推断默认情况下已禁用
rspec-rails 会根据规范在文件系统中的位置自动向规范添加元数据。这对新用户来说令人困惑,并且对于一些资深用户来说并不理想。
在 RSpec 3 中,必须明确启用此行为。
# spec/spec_helper.rb
RSpec.configure do |config|
config.infer_spec_type_from_file_location!
end
由于这种假设的行为在教程中非常普遍,因此默认生成的配置仍然启用此行为。
要明确地标记规范而不使用自动推断,请设置 type
元数据。
RSpec.describe ThingsController, type: :controller do
# Equivalent to being in spec/controllers
end
不同的可用类型在每种不同类型的规范中都有记录,例如 控制器规范的文档。
更多信息
提取了 activemodel 模拟支持
mock_model
和 stub_model
已提取到 rspec-activemodel-mocks gem 中。
感谢 Thomas Holmes 完成提取并提供维护新 gem 的帮助。
删除了 webrat 支持
Webrat 支持已被移除。使用 capybara 代替。
匿名控制器改进
rspec-rails 长期以来允许你创建用于测试的匿名控制器。在 RSpec 3 中,它们已经得到了一些改进。
- 默认情况下,它们将从描述的类继承,而不是从
AppplicationController
继承。可以使用infer_base_class_for_anonymous_controllers
配置选项禁用此行为。 - 在使用“非标准”上下文中(例如,使用抽象父类或没有
ApplicationController
时)的许多错误修复。如果你过去在使用匿名控制器时遇到过问题,现在是再次尝试的好时机。
更多信息
- 文档 - 匿名控制器
- rspec-rails #893 - 默认情况下启用推断基本类
- rspec-rails #905 - 修复匿名控制器路由助手
- rspec-rails #924 - 不要假设
ApplicationController
的存在
最后的话
与往常一样,每个子项目的完整变更日志都可用。
RSpec 3 是 RSpec 近 4 年来的第一个主要版本。它代表了来自众多贡献者的巨大工作量。
无论你如何使用 RSpec,我们都希望你喜欢这些新变化,就像我们一样。
感谢 Xavier Shay 帮助撰写这篇博客文章,感谢 Jon Rowe、Sam Phippen 和 Aaron Kromer 校对。