CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Ruby 语言入门/课时/测试与质量
RawBlame
课时目录12 节

Ruby 语言入门

  1. 01Ruby 环境
  2. 02数据类型
  3. 03语法基础
  4. 04控制流
  5. 05方法与 Blocks
  6. 06OOP 与 Mixin
  7. 07元编程
  8. 08IO 与 Enumerable
  9. 09并发
  10. 10Gems 与框架
  11. 11测试与质量
  12. 12综合练习

测试与质量

RSpec/RuboCop。

30 分钟

测试与质量

这一课你将能做到

  1. 用 Minitest 写单元测试,用 RSpec 写行为驱动测试(BDD)
  2. 用 Pry 和 Byebug 调试 Ruby 代码,设置断点和检查变量
  3. 用 RuboCop 统一代码风格,用 YARD 写文档注释

为什么要学

Ruby 社区有「测试文化」——不写测试的 Ruby 代码等于没写完。Rails 的 rails test、CI 流水线里的 rspec、代码审查里的 RuboCop 检查,测试和质量工具是 Ruby 开发者的日常。不写测试,代码不敢重构;不用 RuboCop,团队风格不统一。

人话:测试是「安全网」——改代码时不怕摔死;RuboCop 是「交警」——统一交通规则;Pry 是「透视镜」——运行时看代码内部。三者配合,代码从「能跑」变成「可维护」。

先建立图像

Ruby 质量工具链
┌─────────────┬─────────────────────────────────┐
│ 测试框架      │ Minitest(标准库) / RSpec(BDD)   │
│ 断言库       │ Minitest::Assertions / RSpec::Matchers │
│ Mock/Stub   │ Minitest::Mock / RSpec Mocks       │
│ 调试器       │ Pry(交互式) / Byebug(断点)        │
│ 代码风格     │ RuboCop / Standard                │
│ 文档         │ YARD / RDoc                       │
│ 覆盖率       │ SimpleCov                         │
└─────────────┴─────────────────────────────────┘

跟着做

步骤 1:Minitest——标准库测试

# calculator.rb
class Calculator
  def add(a, b)
    a + b
  end

  def divide(a, b)
    raise ArgumentError, "除数不能为 0" if b.zero?
    a / b
  end
end

# test_calculator.rb
require "minitest/autorun"
require_relative "calculator"

class TestCalculator < Minitest::Test
  def setup
    @calc = Calculator.new
  end

  def test_add
    assert_equal 5, @calc.add(2, 3)
  end

  def test_divide
    assert_equal 2, @calc.divide(10, 5)
  end

  def test_divide_by_zero
    assert_raises(ArgumentError) { @calc.divide(10, 0) }
  end

  def test_add_floats
    assert_in_delta 0.3, @calc.add(0.1, 0.2), 0.0001
  end
end

# 运行:ruby test_calculator.rb

Minitest 断言速查:

assert_equal expected, actual      # 相等
assert_nil obj                     # 为 nil
assert_includes collection, obj    # 包含
assert_raises(Exception) { ... }   # 抛异常
assert_in_delta expected, actual, delta  # 浮点数近似
refute condition                   # 断言为假

步骤 2:RSpec——行为驱动测试

# Gemfile
# gem "rspec", "~> 3.12"

# spec/calculator_spec.rb
require_relative "../calculator"

RSpec.describe Calculator do
  let(:calc) { Calculator.new }   # 懒加载,每个示例独立

  describe "#add" do
    it "返回两数之和" do
      expect(calc.add(2, 3)).to eq(5)
    end

    it "支持浮点数" do
      expect(calc.add(0.1, 0.2)).to be_within(0.0001).of(0.3)
    end
  end

  describe "#divide" do
    it "返回商" do
      expect(calc.divide(10, 5)).to eq(2)
    end

    it "除数为 0 时抛出 ArgumentError" do
      expect { calc.divide(10, 0) }.to raise_error(ArgumentError, "除数不能为 0")
    end
  end
end

# 运行:rspec spec/calculator_spec.rb
# 格式化输出:rspec --format doc

RSpec 匹配器速查:

expect(actual).to eq(expected)           # 值相等
expect(actual).to be > 5                 # 比较
expect(actual).to be_truthy              # 真值
expect(actual).to be_nil                 # nil
expect(collection).to include(item)      # 包含
expect { ... }.to raise_error(Error)     # 异常
expect { ... }.to change { obj.attr }.by(1)  # 状态变化

步骤 3:Mock 与 Stub

# RSpec Mocks
RSpec.describe OrderService do
  it "调用支付网关" do
    gateway = instance_double(PaymentGateway)
    allow(gateway).to receive(:charge).and_return(success: true)

    service = OrderService.new(gateway)
    result = service.process(100)

    expect(result).to be true
    expect(gateway).to have_received(:charge).with(100)
  end
end

# Minitest Mock
require "minitest/mock"

def test_with_mock
  mock = Minitest::Mock.new
  mock.expect(:charge, { success: true }, [100])

  service = OrderService.new(mock)
  service.process(100)

  mock.verify   # 验证 mock 被正确调用
end

步骤 4:Pry 与 Byebug 调试

# gem install pry pry-byebug

# 在代码中打断点
require "pry"

def complex_calculation(data)
  result = data.map { |x| x * 2 }
  binding.pry   # 程序暂停,进入交互式控制台
  result.sum
end

# 进入 Pry 后常用命令:
# ls          # 列出当前作用域变量
# cd obj      # 进入对象内部
# show-doc    # 查看方法文档
# edit        # 编辑当前文件
# continue    # 继续执行(Byebug)
# step/next   # 单步执行(Byebug)

# Byebug 命令行调试
# ruby -r byebug script.rb
# 或在代码中写 byebug

步骤 5:RuboCop 与代码风格

# 安装
gem install rubocop

# 检查当前目录
rubocop

# 自动修复(安全)
rubocop -a

# 自动修复(包括有风险)
rubocop -A

# 生成配置文件
rubocop --auto-gen-config

.rubocop.yml 示例:

AllCops:
  TargetRubyVersion: 3.3
  NewCops: enable

Style/FrozenStringLiteralComment:
  Enabled: true

Metrics/MethodLength:
  Max: 20

Metrics/ClassLength:
  Max: 150

常见 RuboCop 规则:

规则检查内容
Style/StringLiterals单引号 vs 双引号
Metrics/MethodLength方法长度
Metrics/AbcSize方法复杂度
Lint/UnusedVariable未使用变量
Style/Documentation类/模块缺少注释

讲清楚要点

  1. Minitest 是标准库,RSpec 是 DSL:Minitest 随 Ruby 自带,语法直接;RSpec 用 describe/context/it 组织测试,读起来像自然语言。Rails 默认用 Minitest,社区项目多用 RSpec。
  2. let vs before:RSpec 的 let 是懒加载(用到才创建),before 是每个示例前执行。let 更灵活,但过度嵌套会让测试难以追踪。
  3. Mock 是「假对象」,Stub 是「假方法」:Mock 验证「方法被调用」(行为验证);Stub 只控制「方法返回值」(状态验证)。RSpec 的 instance_double 会验证方法签名,比 double 更安全。
  4. RuboCop 的 -a 和 -A:-a 只修复「安全」的规则(如空格、引号);-A 包括「有风险」的规则(如方法长度拆分)。CI 里用 -a,本地大胆用 -A。
  5. SimpleCov 覆盖率不是目标:100% 覆盖率不等于无 bug,只代表代码被执行过。追求「有意义的测试」而非「覆盖率的数字」。

你可能会懵的地方

  • Minitest 和 RSpec 哪个好? 没有绝对。Minitest 更轻量、标准库自带;RSpec 表达力强、生态丰富。Rails 项目默认 Minitest,但换成 RSpec 也很常见。团队统一即可。

  • let 和 let! 的区别? let 懒加载,第一次引用时执行;let! 立即执行,在每个示例前强制创建。有副作用(如写数据库)的用 let!。

  • RSpec 的 subject 是什么? subject 是被测试对象的默认实例。subject { described_class.new } 或自动推断。is_expected.to eq(5) 等价 expect(subject).to eq(5)。

  • Pry 和 irb 的区别? Pry 是增强版 irb:语法高亮、命令系统(show-doc、edit)、插件生态(pry-byebug、pry-rails)。调试时 Pry 比 irb 强大得多。

  • RuboCop 和 Standard 的区别? RuboCop 可配置,规则细;Standard 是 RuboCop 的「零配置」封装,意见统一,不让你纠结配置。新项目推荐 Standard,老项目用 RuboCop 渐进改造。

错法对照

错法问题正解
测试里用 puts 调试输出污染、无法断言用 binding.pry 或 expect 断言
assert_equal expected, actual 参数写反失败信息 confusing第一个参数是 expected
RSpec 里 let 嵌套 5 层测试难以追踪数据来源减少嵌套,用 before 显式创建
用 double 不验证方法签名方法改名后测试还过用 instance_double 或 class_double
RuboCop 全部 -A 自动修复可能改坏代码逻辑先 -a,再人工 review -A 的变更

卡点急救

  1. 测试随机失败:顺序依赖或共享状态。用 rspec --order random 发现,检查 let 和 before 的副作用。
  2. Pry 进不去:require "pry" 没写,或代码路径没走到。加 puts "here" 确认执行到断点。
  3. RuboCop 报大量历史遗留问题:用 rubocop --auto-gen-config 生成 .rubocop_todo.yml,逐步修复。

面试常问

  • TDD 和 BDD 的区别? TDD(测试驱动开发)先写测试再写实现,关注「代码是否正确」;BDD(行为驱动开发)用自然语言描述行为,关注「系统行为是否符合预期」。RSpec 是 BDD 框架,describe/it 语法让测试读起来像需求文档。

  • Mock 和 Stub 的区别? Stub 控制「方法的返回值」(状态验证);Mock 验证「方法是否被调用、参数是否正确」(行为验证)。Stub 是「 passive 的」,Mock 是「active 的」。

  • 如何写出可维护的测试? 测试代码也要 DRY,但不要过度抽象;每个测试只验证一件事;测试名描述行为而非实现;用 let 管理测试数据,但避免深层嵌套;集成测试比单元测试更能发现问题。

自测三问

  1. let 和 before 在 RSpec 中的执行时机有什么区别?
  2. instance_double 比 double 更安全,为什么?
  3. RuboCop 的 -a 和 -A 分别修复什么类型的规则?

小结一句话

测试是安全网,Pry 是透视镜,RuboCop 是交警——三者配合,Ruby 代码从「能跑」进化到「可维护、可重构、可协作」。

延伸

  • RSpec 官方文档
  • RuboCop 官方文档
  • 下一课:综合练习——用 Bundler 创建 Gem,写一个完整的 CLI 小项目。
加载本节练习…
← 上一课Gems 与框架下一课 →综合练习