RSpec 3.4 已经发布了!

Myron Marston

2015 年 11 月 13 日

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

RSpec 继续是一个社区驱动的项目,来自世界各地的贡献者参与其中。此版本包含来自近 50 位不同贡献者的 500 多个提交和 160 个合并的拉取请求!

感谢所有帮助完成此版本发布的人!

值得注意的变更

核心:二分查找算法改进

RSpec 3.3 发布了一个新的 --bisect 选项,用于在追踪排序依赖来源时识别最小的重现命令。核心二分查找算法使用了一种朴素的排列方法:在每一轮中,它首先尝试一半的示例,然后尝试另一半,然后尝试一半示例的每种组合,直到找到可以安全忽略的一半。这通常效果不错,但有一些糟糕的最坏情况行为。特别是,如果你的排序依赖中涉及多个罪魁祸首,则可能需要很多次组合才能命中包含两个罪魁祸首的组合。同样,如果算法已经到达了一半以上剩余示例是罪魁祸首的点,它将穷尽地尝试每种组合,直到不再剩下任何示例——这将花费很长时间。

在 RSpec 3.4 中,二分查找算法更智能。它现在使用一种递归方法,旨在最大程度地减少识别罪魁祸首所需的尝试次数。对新算法的早期反馈非常积极:Sam Livingston-Gray 报告说,3.3 二分查找运行了一整晚都没有完成,但使用新算法,它只在 20 分钟内就完成了

感谢 Simon Coffey 的实现!如果你想了解更多信息,我建议你 查看他的 PR——它包含一些非常有用的图表,解释了新算法的工作原理。

核心:失败输出改进

良好的失败输出一直是 RSpec 的优先事项,但在 3.4 中它得到了很大的改进,并且在以下几个方面有所改进

多行代码片段

RSpec 在失败输出中包含来自预期失败的代码片段。在 RSpec 3.4 之前,只要你的预期适合一行,这通常效果不错。如果你将其格式化为多行,例如这样

expect {
  MyNamespace::MyClass.some_long_method_name(:with, :some, :arguments)
}.to raise_error(/some error snippet/)

…那么失败将只打印第一行(expect {),因为这是异常中包含的堆栈帧。在 RSpec 3.4 中,我们现在会从标准库加载 Ripper(如果它在标准库中可用),以解析源代码并确定要包含用于完整预期表达式的行数。对于像上面的片段这样的情况,失败输出现在将包含整个表达式。

还有一个新的配置选项可以与之搭配:config.max_displayed_failure_line_count,它默认为 10,并设置了片段大小的限制。

感谢 Yuji Nakayama 的 实现

安装 coderay 以进行语法高亮

更进一步,如果 coderay gem 可用,RSpec 3.4 将使用它在你的终端中对代码片段进行语法高亮。这是一个使用上面的代码片段的示例

Failure with syntax highlighting

更好的失败源代码检测

RSpec 通过查看异常堆栈跟踪以查找合适的框架来查找失败代码片段。我们只需要使用顶部堆栈帧,但这通常不是你想要的:当你遇到预期失败时,顶部帧引用的是 RSpec 中的一个链接,其中 RSpec::Expectations::ExpectationNotMetError 被抛出,而你想要看到来自你的 expect 调用位置的代码片段,而不是看到 RSpec 代码的片段。在 RSpec 3.4 之前,我们对此的解决方案相当简单:我们只是查找当前运行的示例文件所在规范的第一个堆栈帧。在某些情况下,这将显示错误的代码片段(例如,当你的示例调用在 spec/support 文件中定义的辅助方法时,真正的失败发生在该文件里)。在其他情况下,它什么也找不到,我们最终显示了 无法从回溯中找到匹配的行 而不是它。

RSpec 3.4 具有更好的查找源代码片段的逻辑:现在我们查找来自 config.project_source_dirs(默认为 libappspec)的第一个框架,如果找不到匹配的框架,我们将退回到第一个堆栈帧。你应该不会再看到 无法从回溯中找到匹配的行 了!

期望:更好的复合失败消息

继续“改进失败输出”主题,rspec-expectations 3.4 为复合期望提供了更好的失败消息。以前,我们会将每个失败消息组合成一行。例如,此期望

expect(lyrics).to start_with("There must be some kind of way out of here")
              .and include("No reason to get excited")

…产生了此难以阅读的失败

1) All Along the Watchtower has the expected lyrics
   Failure/Error: expect(lyrics).to start_with("There must be some kind of way out of here")
     expected "I stand up next to a mountain And I chop it down with the edge of my hand" to start with "There must be some kind of way out of here" and expected "I stand up next to a mountain And I chop it down with the edge of my hand" to include "No reason to get excited"
   # ./spec/example_spec.rb:20:in `block (2 levels) in <top (required)>'

在 RSpec 3.4 中,我们对每个单独的失败消息进行格式化,使其更易于阅读

1) All Along the Watchtower has the expected lyrics
   Failure/Error:
     expect(lyrics).to start_with("There must be some kind of way out of here")
                   .and include("No reason to get excited")

        expected "I stand up next to a mountain And I chop it down with the edge of my hand" to start with "There must be some kind of way out of here"

     ...and:

        expected "I stand up next to a mountain And I chop it down with the edge of my hand" to include "No reason to get excited"
   # ./spec/example_spec.rb:20:in `block (2 levels) in <top (required)>'

期望:向 match 匹配器添加 with_captures

在 RSpec 3.4 中,match 匹配器获得了一种新功能:你可以指定正则表达式捕获。你可以使用新的 with_captures 方法来指定有序捕获

year_regex = /(\d{4})\-(\d{2})\-(\d{2})/
expect(year_regex).to match("2015-12-25").with_captures("2015", "12", "25")

…或者指定命名捕获

year_regex = /(?<year>\d{4})\-(?<month>\d{2})\-(?<day>\d{2})/
expect(year_regex).to match("2015-12-25").with_captures(
  year: "2015",
  month: "12",
  day: "25"
)

感谢 Sam Phippen 和 Jason Karns 共同开发此新功能。

Rails:针对 ActiveJob 的新 have_enqueued_job 匹配器

Rails 4.2 发布了 ActiveJob,rspec-rails 3.4 现在有一个匹配器,允许你指定一段代码会将一个作业排队。它支持一个流利的接口,如果你是一个 rspec-mocks 用户,你会发现它很熟悉

expect {
  HeavyLiftingJob.perform_later
}.to have_enqueued_job

expect {
  HelloJob.perform_later
  HeavyLiftingJob.perform_later
}.to have_enqueued_job(HelloJob).exactly(:once)

expect {
  HelloJob.perform_later
  HelloJob.perform_later
  HelloJob.perform_later
}.to have_enqueued_job(HelloJob).at_least(2).times

expect {
  HelloJob.perform_later
}.to have_enqueued_job(HelloJob).at_most(:twice)

expect {
  HelloJob.perform_later
  HeavyLiftingJob.perform_later
}.to have_enqueued_job(HelloJob).and have_enqueued_job(HeavyLiftingJob)

expect {
  HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42)
}.to have_enqueued_job.with(42).on_queue("low").at(Date.tomorrow.noon)

感谢 Wojciech Wnętrzak 实现此功能!

统计

组合

rspec-core

rspec-expectations

rspec-mocks

rspec-rails

rspec-support

文档

API 文档

Cucumber 功能

发行说明

rspec-core 3.4.0

完整变更日志

增强功能

错误修复

rspec-expectations 3.4.0

完整变更日志

增强功能

错误修复

rspec-mocks 3.4.0

完整变更日志

增强功能

错误修复

rspec-rails 3.4.0

完整变更日志

增强功能

错误修复

rspec-support 3.4.0

完整变更日志

增强功能

错误修复