rspec-core 构建状态 代码质量

rspec-core 提供了编写代码行为的可执行示例的结构,以及一个 rspec 命令,其中包含用于限制运行哪些示例和定制输出的工具。

安装

gem install rspec      # for rspec-core, rspec-expectations, rspec-mocks
gem install rspec-core # for rspec-core only
rspec --help

想要针对 main 分支运行?您还需要包含依赖的 RSpec 库。将以下内容添加到您的 Gemfile

%w[rspec rspec-core rspec-expectations rspec-mocks rspec-support].each do |lib|
  gem lib, :git => "https://github.com/rspec/#{lib}.git", :branch => 'main'
end

基本结构

RSpec 使用“describe”和“it”这两个词,以便我们可以表达类似对话的概念。

"Describe an order."
"It sums the prices of its line items."
RSpec.describe Order do
  it "sums the prices of its line items" do
    order = Order.new
    order.add_entry(LineItem.new(:item => Item.new(
      :price => Money.new(1.11, :USD)
    )))
    order.add_entry(LineItem.new(:item => Item.new(
      :price => Money.new(2.22, :USD),
      :quantity => 2
    )))
    expect(order.total).to eq(Money.new(5.55, :USD))
  end
end

describe 方法创建一个 ExampleGroup。在传递给 describe 的代码块中,您可以使用 it 方法声明示例。

在幕后,一个示例组是一个类,在其中传递给 describe 的代码块被评估。传递给 it 的代码块在该类的实例上下文中被评估。

嵌套组

您也可以使用 describecontext 方法声明嵌套组。

RSpec.describe Order do
  context "with no items" do
    it "behaves one way" do
      # ...
    end
  end
  context "with one item" do
    it "behaves another way" do
      # ...
    end
  end
end

嵌套组是外部示例组类的子类,为其提供您期望的继承语义。

别名

您可以使用 describecontext 声明示例组。对于顶级示例组,describecontext 可从 RSpec 中使用。为了向后兼容,它们也可以从 main 对象和 Module 中使用,除非您禁用了猴子补丁。

您可以在一个组中使用 itspecifyexample 中的任何一个声明示例。

共享示例和上下文

使用 shared_examples 声明一个共享示例组,然后使用 include_examples 将其包含在任何组中。

RSpec.shared_examples "collections" do |collection_class|
  it "is empty when first created" do
    expect(collection_class.new).to be_empty
  end
end
RSpec.describe Array do
  include_examples "collections", Array
end
RSpec.describe Hash do
  include_examples "collections", Hash
end

几乎任何可以在示例组中声明的都可以声明在共享示例组中。这包括 beforeafteraround 钩子、let 声明以及嵌套组/上下文。

您也可以使用 shared_contextinclude_context 这两个名称。它们与 shared_examplesinclude_examples 几乎相同,只是在共享钩子、let 声明、辅助方法等时提供更准确的命名,但没有示例。

如果您希望在整个 RSpec 套件中重用共享示例或上下文,您可以将它们定义在独立的 *.rb 文件中(例如,spec/support/shared_examples/definition.rb)。但是您必须手动 require 它们(除非您自己设置,否则不会自动加载 spec/support/ 目录)。

元数据

rspec-core 为每个示例和组存储一个元数据哈希,其中包含它们的描述、声明它们的位置等。此哈希为 rspec-core 的许多功能提供了支持,包括输出格式器(访问描述和位置)以及过滤 before 和 after 钩子。

尽管您可能永远不需要它,除非您正在编写扩展程序,但您可以从示例中以这种方式访问它

it "does something" do |example|
  expect(example.[:description]).to eq("does something")
end

described_class

当一个类被传递给 describe 时,您可以使用 described_class 方法从示例中访问它,它是一个 example.metadata[:described_class] 的包装器。

RSpec.describe Widget do
  example do
    expect(described_class).to equal(Widget)
  end
end

这在扩展程序或共享示例组中很有用,在这些组中,特定类是未知的。以上面的集合共享示例组为例,我们可以使用 described_class 稍微清理一下它

RSpec.shared_examples "collections" do
  it "is empty when first created" do
    expect(described_class.new).to be_empty
  end
end
RSpec.describe Array do
  include_examples "collections"
end
RSpec.describe Hash do
  include_examples "collections"
end

关于范围的一句话

RSpec 有两个范围

  • 示例组:示例组由 describecontext 代码块定义,该代码块在加载规范文件时被热切地评估。该代码块在 RSpec::Core::ExampleGroup 的子类或当您嵌套它们时父示例组的子类的上下文中被评估。
  • 示例:示例(通常由 it 代码块定义)以及任何其他具有每个示例语义的代码块(例如 before(:example) 钩子)在该示例所属的示例组类的实例上下文中被评估。示例不会在加载规范文件时执行;相反,RSpec 会等待运行所有示例,直到所有规范文件都被加载,此时它可以应用过滤、随机化等。

为了使这更加具体,请考虑以下代码片段

RSpec.describe "Using an array as a stack" do
  def build_stack
    []
  end
  before(:example) do
    @stack = build_stack
  end
  it 'is initially empty' do
    expect(@stack).to be_empty
  end
  context "after an item has been pushed" do
    before(:example) do
      @stack.push :item
    end
    it 'allows the pushed item to be popped' do
      expect(@stack.pop).to eq(:item)
    end
  end
end

在幕后,这大致等同于

class UsingAnArrayAsAStack < RSpec::Core::ExampleGroup
  def build_stack
    []
  end
  def before_example_1
    @stack = build_stack
  end
  def it_is_initially_empty
    expect(@stack).to be_empty
  end
  class AfterAnItemHasBeenPushed < self
    def before_example_2
      @stack.push :item
    end
    def it_allows_the_pushed_item_to_be_popped
      expect(@stack.pop).to eq(:item)
    end
  end
end

为了运行这些示例,RSpec 大致会执行以下操作

example_1 = UsingAnArrayAsAStack.new
example_1.before_example_1
example_1.it_is_initially_empty
example_2 = UsingAnArrayAsAStack::AfterAnItemHasBeenPushed.new
example_2.before_example_1
example_2.before_example_2
example_2.it_allows_the_pushed_item_to_be_popped

rspec 命令

当您安装 rspec-core gem 时,它会安装 rspec 可执行文件,您将使用它来运行 rspec。rspec 命令附带了许多有用的选项。运行 rspec --help 以查看完整的列表。

存储命令行选项 .rspec

您可以在项目根目录中的 .rspec 文件中存储命令行选项,rspec 命令会像您在命令行中输入一样读取它们。

入门

从您对系统期望的行为的简单示例开始。在您编写任何实现代码之前执行此操作。

# in spec/calculator_spec.rb
RSpec.describe Calculator do
  describe '#add' do
    it 'returns the sum of its arguments' do
      expect(Calculator.new.add(1, 2)).to eq(3)
    end
  end
end

使用 rspec 命令运行它,并观察它失败

$ rspec spec/calculator_spec.rb
./spec/calculator_spec.rb:1: uninitialized constant Calculator

通过定义 Calculator 类的骨架来解决故障

# in lib/calculator.rb
class Calculator
  def add(a, b)
  end
end

确保在规范中要求实现文件

# in spec/calculator_spec.rb
# - RSpec adds ./lib to the $LOAD_PATH
require "calculator"

现在再次运行规范,并观察期望失败

$ rspec spec/calculator_spec.rb
F
Failures:
  1) Calculator#add returns the sum of its arguments
     Failure/Error: expect(Calculator.new.add(1, 2)).to eq(3)
       expected: 3
            got: nil
       (compared using ==)
     # ./spec/calculator_spec.rb:6:in `block (3 levels) in <top (required)>'
Finished in 0.00131 seconds (files took 0.10968 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/calculator_spec.rb:5 # Calculator#add returns the sum of its arguments

实现最简单的解决方案,通过将 Calculator#add 的定义更改为

def add(a, b)
  a + b
end

现在再次运行规范,并观察它通过

$ rspec spec/calculator_spec.rb
.
Finished in 0.000315 seconds
1 example, 0 failures

使用 documentation 格式化程序查看生成的规范

$ rspec spec/calculator_spec.rb --format doc
Calculator
  #add
    returns the sum of its arguments
Finished in 0.000379 seconds
1 example, 0 failures

贡献

设置好环境后,您需要 cd 到您想在其中工作的任何库的工作目录。从那里您可以运行规范和黄瓜功能,并创建补丁。

注意:您不需要使用 rspec-dev 来处理特定的 RSpec 库。您可以将每个 RSpec 库视为一个独立的项目。

另请参阅