RSpec 入门教程

FreeGuideOnline 最新 2026-07-14

bash ruby -v


### 2. 初始化项目
在你的项目目录中,如果还没有 Gemfile,先创建:
```bash
bundle init

3. 添加 RSpec

编辑 Gemfile,加入 RSpec:

gem 'rspec', '~> 3.12'

然后执行安装:

bundle install

4. 初始化 RSpec 配置

bundle exec rspec --init

这会生成 .rspecspec/spec_helper.rb 文件。

第一个 RSpec 测试

假设你有一个计算器类,我们为它的 add 方法写测试。

创建待测试类

lib/calculator.rb 中:

class Calculator
  def add(a, b)
    a + b
  end
end

编写测试规格

spec/calculator_spec.rb 中:

require 'calculator'

RSpec.describe Calculator do
  describe '#add' do
    it 'returns the sum of two numbers' do
      calc = Calculator.new
      expect(calc.add(2, 3)).to eq(5)
    end
  end
end

运行测试

bundle exec rspec

看到绿色输出表示测试通过。

RSpec 核心结构

describecontext

  • describe 用于组织一组相关测试,通常用来描述类名或方法名。
  • contextdescribe 的别名,用于区分不同场景(当输入不同时)。
describe Calculator do
  describe '#divide' do
    context 'when divisor is not zero' do
      it 'returns the division result' # ...
    end

    context 'when divisor is zero' do
      it 'raises an error' # ...
    end
  end
end

itexample

it 定义一个具体的测试用例(也叫 example)。其后的字符串描述预期行为。

期望(Expectations)

expect(实际值).to 匹配器(期望值) 是 RSpec 的核心断言语法。

常用匹配器:

  • eq:严格相等(比 == 更严格)
  • be:用于比较同一对象 / 布尔值
  • be_truthy / be_falsey
  • include:检查集合是否包含某个元素
  • match:正则匹配
  • raise_error:检查是否抛出异常
expect(10).to eq(10)
expect(true).to be true
expect([1,2,3]).to include(2)
expect { 1 / 0 }.to raise_error(ZeroDivisionError)

测试辅助机制

beforeafter 钩子

before(:each) 在每个测试前运行,常用于初始化状态。

describe Game do
  before(:each) do
    @game = Game.new
    @game.start
  end

  it 'has initial score zero' do
    expect(@game.score).to eq(0)
  end
end

before(:all) 在整个 describe 块前运行一次(谨慎使用,避免状态共享)。

letlet!

let 用于干净地定义延迟求值的变量,适合替代实例变量。

describe User do
  let(:user) { User.new(age: 25) }

  it 'is not underage' do
    expect(user).not_to be_underage
  end
end
  • let 所定义的变量,在第一次访问时才会创建,且在同一 example 内会缓存。
  • let! 则会在每个 example 运行前立即创建(类似 before 但更精确)。

subject

subject 用于定义测试主体,使测试更简洁。

describe Array do
  subject { [1, 2, 3] }

  it 'has length 3' do
    expect(subject.length).to eq(3)
  end

  # 也可以使用懒人写法 it { is_expected.to ... }
  it { is_expected.to include(2) }
end

测试替身 (Mocks & Stubs)

RSpec 内置了双替身(doubles)功能,用于隔离被测对象的依赖。

方法桩 (Stubs)

使用 allow 固定某个对象方法的返回值。

weather_api = double('WeatherAPI')
allow(weather_api).to receive(:temperature).and_return(22)

report = WeatherReport.new(weather_api)
expect(report.describe).to eq('Warm')

消息期望 (Mock)

使用 expect 来验证对象是否收到了某个消息。

expect(weather_api).to receive(:temperature).and_return(22)

上述代码在测试结束时,如果没有调用 temperature,测试就会失败。

组织测试文件与标签

目录结构

推荐遵循约定:

project/
├── lib/
│   └── calculator.rb
└── spec/
    ├── calculator_spec.rb
    └── spec_helper.rb

使用标签过滤测试

可以在 describeit 上添加标签,运行时过滤。

it 'runs a slow process', :slow do
  # ...
end

运行特定标签测试:

bundle exec rspec --tag slow

排除标签订:

bundle exec rspec --tag ~slow

测试驱动开发(TDD)实践

  1. 写一个失败的测试:描述你希望看到的行
  2. 编写最少代码让测试通过:不要过度设计
  3. 重构:优化代码结构,确保测试仍然通过

RSpec 的报告格式能清晰展示失败信息,帮你快速定位问题。使用 --format documentation 可以看到详细描述。

常用配置与技巧

总是在 spec_helper 中 require 项目文件

可以在 spec_helper.rb 顶部引入 lib 路径,避免每次手动 require。

$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
require 'calculator'

开启 --warnings--order random

.rspec 文件里可以添加:

--format documentation
--color
--order random