RSpec 3 计划

Myron Marston

2013年7月15日

更新:现在已经有了日语翻译

RSpec 2.0 于 2010 年 10 月发布。自那以后的近三年时间里,我们能够不断改进 RSpec,而无需进行重大更改,但我们已经达到一个阶段,RSpec 积累了不少代码冗余,这是为了保持与早期 2.x 版本向后兼容性而导致的。

RSpec 2.14 将是最后一个 RSpec 2 功能版本。(我们可能会发布一些错误修复补丁版本)。我们正在开始 RSpec 3 的开发工作,并且我想分享我们对 RSpec 未来方向的思考。

当然,这一切都还没有确定,最终,RSpec 成为一个成功的项目,是因为所有使用它的人。因此,如果您对我们应该采取的 RSpec 3 方向有任何想法,请说出来!

将被移除的内容

不再支持 1.8.6 和 1.9.1

RSpec 2.x 继续支持 Ruby 1.8.6,即使 MRI 团队已经不再支持该版本。作为 Ruby 生态系统中重要的测试基础设施的一部分,我们认为让 gem 作者决定何时放弃对旧版本 Ruby 的支持很重要,而不是因为他们选择的测试框架不再支持其中一个版本而被迫过早地放弃支持。

Ruby 1.8.6 和 1.9.1 已经 近两年 没有在 Travis 上可用,而且没有 CI 服务器在这些版本上运行我们的构建,继续支持它们变得非常困难。实际上,在过去几年中,我们实际上只是“半支持”了这些 Ruby 版本:当用户报告这些 Ruby 版本上的问题时,我们会修复它们,但我们没有在支持方面投入更多精力。

因此,现在是时候放弃对这些版本的支持了。我们计划在 RSpec 3 中继续支持 1.8.7、1.9.2 和所有更新的 Ruby 版本。鉴于 1.8.7 现在已经是遗留版本,我们可能会在 RSpec 4 中放弃对 1.8.7 的支持,尽管如果/当 Travis 在此之前停止支持它时,我们只能以与我们“半支持”1.8.6 相同的方式“半支持”它。

核心:its 将被移到一个外部 gem 中

我之前写过这方面的内容,所以这里就不赘述了。我们计划将 its 从 rspec-core 移到一个外部 gem 中。

期望:have(x).items 匹配器将被移到一个外部 gem 中

RSpec 的起源是在 Cucumber/Gherkin 出现之前,它的早期目标之一是使用项目利益相关者能够理解的自然语言来表达事物。在早期,像 team.should have(9).players 这样的表达式对于项目的目标来说是有意义的。从那时起,Cucumber/Gherkin 已经成为针对利益相关者测试的更好替代方案,而 RSpec 现在很少用于该目的。have(x).items 匹配器系列(包括 have_at_least(x).itemshave_at_most(x).items 子级)过于复杂,而像 expect(team.players.size).to eq(9) 这样的简单表达式就足够了。

我们计划将这些匹配器从 rspec-expectations 移到一个外部 gem 中。

核心:不再有显式调试器支持

RSpec 长期以来一直支持 -d / --debug 命令行选项,用于通过 ruby-debug gem 启用调试器。但是,如今 ruby-debug 并非唯一(甚至不是主要的)正在使用的调试 gem。debugger 已经成为 MRI 1.9.2+ 事实上的标准调试 gem,许多开发人员更喜欢使用 pry 进行调试。其他 Ruby 解释器(如 Rubinius)具有 自己的调试器

我们计划在 RSpec 3 中移除显式调试器支持。除了移除命令行选项之外,我们还将移除 Kernel 中调试器的猴子补丁(当 ruby-debug 未加载时),因此当调试器未加载时,您将从 debugger 获取 NoMethodError

如果您想继续使用命令行选项加载调试器,您可以使用 require 标志(-r),使用类似 -rdebugger 的选项。

核心:不再集成 RCov

RSpec::Core::RakeTask 长期以来一直具有一些 RCov 选项。RCov 仅适用于 MRI 1.8,如今大多数 Ruby 开发人员使用 SimpleCov 来满足其代码覆盖率需求。SimpleCov 与 RSpec(或任何测试框架)的集成非常简单,无需 RSpec 本身提供显式支持。

核心:Autotest 集成将被移到一个外部 gem 中

Autotest 曾经是主要的 Ruby 持续测试运行器。如今,guard 似乎是更受欢迎的选择,RSpec 的 Autotest 集成没有理由保留在 rspec-core 中。

核心:TextMate 格式化程序将被移到 TextMate 捆绑包中

多年来,TextMate 是 Ruby 开发人员使用的最受欢迎的文本编辑器。RSpec 长期以来一直有一个 特定于 TextMate 的格式化程序。如今,TextMate 在 Ruby 开发人员中的受欢迎程度远不如以前,TextMate 格式化程序没有充分的理由保留在 rspec-core 中。

大量弃用

RSpec 2.14 包含了许多在过去几年中已被弃用的内容。我们计划移除几乎所有已弃用的 API 和功能。

旧的期望/模拟语法怎么样?

RSpec 2.11 引入了 一种新的基于 expect 的 rspec-expectations 语法。 在 RSpec 2.14 中,我们更新了 rspec-mocks 以使用类似的语法。自从引入新的语法以来,我收到了许多关于我们何时会弃用或移除旧的基于 should 的语法的问题。

虽然我不会说“永远不会”(谁知道未来会发生什么?),但我们目前没有任何计划完全移除旧语法。用户在使用旧语法的代码上投入了多年的时间,虽然我们建议使用新的语法(特别是对于新项目),但如果我们很快移除旧语法,将会对用户不利。这也并非显著的维护负担。

对于 RSpec 3,我们考虑过默认禁用旧语法,迫使用户选择使用它。但是,我认为这样做对通过非最新教程接触 RSpec 的新用户不利。对于第一次尝试 RSpec 的人来说,从教程中复制的示例得到 NoMethodError 非常令人沮丧。有经验的用户可以轻松地禁用旧语法,而新用户可能没有足够的 RSpec 知识来知道如何启用他们的教程使用的旧语法。

话虽如此,我们确实希望鼓励人们切换到新的语法,因此我们计划让 RSpec 3 在第一次使用任何旧语法方法(shouldshould_notshould_receive 等)时打印警告,除非已显式启用 should 语法。这应该会促使人们转向新的语法,同时保持 RSpec 对新用户友好,并将为在 RSpec 4 中默认禁用旧语法铺平道路。

新增内容

零猴子补丁模式!

从历史上看,RSpec 广泛使用猴子补丁来创建其可读语法,为每个对象添加了 describeshared_examples_forshared_contextshouldshould_notshould_receiveshould_not_receivestub 等方法。在最近的几个 2.x 版本中,我们一直在努力减少 RSpec 执行的猴子补丁数量。

如上所述,我们将在 3.0 中移除 RSpec 的猴子补丁 Kernel#debugger。我们还计划提供一个配置选项来移除顶层 DSL 方法(describeshared_examples_for 等)到 mainModule 的猴子补丁,而是要求您在这些方法前加上 RSpec.

RSpec.describe MyClass do
  # Within an example group you'll still be able to use
  # a bare `describe`:
  describe "#some_method" do
  end

  # And you'll be able to use a bare `shared_examples_for`:
  shared_examples_for "something" do
  end
end

RSpec.shared_examples_for "some behavior" do
end

最终结果将是一组配置选项(rspec-expectations 一个,rspec-mocks 一个,rspec-core 一个),这将为 RSpec 提供零猴子补丁模式。(我们也可能会提供一个统一的配置选项,它将设置所有三个选项)。

我们计划让这些配置选项在 RSpec 4.0 中成为默认值,这样 RSpec 4.0 将默认拥有零猴子补丁。

模拟:测试双重接口验证

不幸的是,让您的测试双重与它们模拟的实际接口不同步非常容易。当您重命名方法或更改方法期望的参数数量时,您很容易忘记更新作为已更改类的替身使用的测试双重。

我一直是 rspec-fire 解决这个问题方法的粉丝。我计划将它的一个版本移植到 rspec-mocks 中。

请查看 github 问题,在那里我们正在讨论这个问题的全部细节(此功能的 API 和语义当然还没有确定,所以请在该问题上表达您的想法!)。

期望:完全可组合的匹配器

在 RSpec 2.13 中,我们 添加了支持include 匹配器接受一个匹配器列表以进行匹配。这种可组合性非常有用,我们计划在 RSpec 3 中将其扩展到所有匹配器。例如,您可以使用类似的表达式:

expect { |b|
  some_object.do_something(&b)
}.to yield_with_args(include(match(/foo/), match(/bar/)))

这表达了一个详细的期望:“我希望 some_object.do_something 以一个包含匹配 /foo/ 的字符串和匹配 /bar/ 的字符串的集合来产生。”

我们还在考虑添加匹配器别名,这些别名在以这种方式组合时更易读,这样您就可以这样写:

expect { |b|
  some_object.do_something(&b)
}.to yield_with_args(a_collection_including(a_string_matching(/foo/),
                                            a_string_matching(/bar/)))

有关更多详细信息或对这个问题发表意见,请查看 github 问题

核心:格式化程序 API 改进

当前用于向格式化程序通知测试套件进度的 API 在添加新的通知和更改现有通知方面被证明有些僵化。我们计划在几个方面进行更改。

这些更改将使我们能够进行进一步的改进,这些改进在 2.x 版本中无法实现。我们还计划在 RSpec 3 中提供一个兼容性层,该层将包装针对旧 API 编写的格式化程序并将其适配到新 API,以便用户在依赖旧格式化程序时能够更轻松地升级。

有关更多详细信息,请查看 github 问题

核心:DSL 方法将产生示例

在 RSpec 2 中,当前运行的示例被公开为 example。它可以用来 访问示例的元数据。这偶尔会 造成问题,因为用户无意中定义了自己的 example 方法。在 RSpec 3 中,我们将删除 example 方法,选择从在示例上下文中运行的每个 DSL 方法中产生示例

describe MyClass do
  before(:each) { |example| }
  subject       { |ex| }
  let(:user)    { |ex| User.find(ex.metadata[:user_id]) }

  # before(:all) will NOT yield an example

  it "can access the current example using a block local" do |example|
    # do something with `example`
  end
end

我们知道,这可能会给依赖于使用 example API 的 gem(例如 Capybara)的用户带来升级方面的麻烦。我们正在讨论如何使升级过程更加顺利,无论是对于用户还是 gem 作者。有关更多信息,请参阅 github 问题

期望:Matcher 协议和自定义 matcher API 更改

虽然 RSpec 一直在远离其基于 should 的语法,但 matcher 协议和自定义 matcher API 并没有相应地改变。matcher 协议仍然依赖于诸如 failure_message_for_shouldfailure_message_for_should_not 之类的方法,而自定义 matcher API 则具有诸如 match_for_shouldmatch_for_should_not 之类的方法。

在 RSpec 3 中,我们希望更改 matcher 协议和自定义 matcher API,使其不再以 should 术语进行表述,同时仍然保留向后兼容层,以便现有 matcher 能够继续工作,并计划在 RSpec 4 中删除该兼容层。我们还不确定新的 API 会是什么样子;如果您有想法,请 在 github 问题中发表您的意见

模拟:any_instance 块实现将产生接收者

当使用 any_instance 伪造方法时,您可以像普通伪造一样传递一个块实现。但是,如果您想在块中访问接收者(即接收消息的实例),则没有办法实现这一点。在 RSpec 3 中,我们将纠正这个疏忽,接收者将作为第一个块参数产生。

allow_any_instance_of(User).to receive(:age) do |user|
  ((Date.today - user.birthdate) / 365).floor
end

为了向后兼容,我们将添加一个配置选项来禁用此行为。

升级路径

尽管 RSpec 3.0 将是一个主要版本,它将允许我们自 2010 年以来首次进行有意性的重大更改,但对我们来说,现有测试套件的升级路径尽可能简单至关重要。为此,我们计划发布一个 2.99 版本,它将纯粹是为了帮助用户升级而存在。以下是我们考虑的内容。

2.99 版本将是一个重要的步骤,在升级过程中不应跳过它。它将为您提供一个专门针对您的测试套件对 RSpec 的使用的升级清单,为您提供比梳理变更日志试图找出 RSpec 3 中的所有更改更简单、更有效的方式来进行升级。

开发和发布计划

我们已经开始在每个 RSpec 存储库的主分支上开发 RSpec 3。我们还拥有一个 2-14-maintenance 分支用于 2.14 更改(即用于可能的补丁版本)和一个 2-99-maintenance 分支用于将要进入 2.99 的更改。我们计划在向最终 3.0 版本发布的过程中进行多次发布候选版(以及可能的一些测试版)。

我不会猜测我们何时发布 RSpec 3。经验告诉我,软件发布日期估计总是错误的 :(。

“我该如何帮助?”

当前的 RSpec 核心团队(DavidAndyJonSamBradley我自己)将推动 3.0 版本的工作……但一如既往,我们很乐意得到社区的帮助。以下是一些您可以提供帮助的具体方法。