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

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

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

学习

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

更多

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

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Ruby on Rails 实战入门/课时/测试与调试
RawBlame
课时目录12 节

Ruby on Rails 实战入门

  1. 01Rails 概览与项目结构
  2. 02路由设计
  3. 03控制器
  4. 04Active Record 模型
  5. 05查询与优化
  6. 06数据库适配
  7. 07视图与组件
  8. 08表单
  9. 09认证与授权
  10. 10资产与后台任务
  11. 11测试与调试
  12. 12部署与运维

测试与调试

RSpec、FactoryBot 与 Capybara。

30 分钟

测试与调试

这一课你将能做到

  1. 用 Rails 默认的 Minitest 写模型测试和请求测试,跑通 bin/rails test
  2. 用 Fixtures 准备测试数据,说清楚 fixtures 与工厂模式的取舍
  3. 用 rails console、debug gem 和日志快速定位问题,不盲猜

为什么要学

测试是工程质量的刹车片,不是交付后的锦上添花。Rails 把测试框架、数据准备、运行命令全内置了,创建模型或 scaffold 时测试文件已自动生成——你的成本只剩写断言。和本站对比:Spring Boot 需要手动引入 JUnit + Mockito + 测试配置文件;Rails 开箱即用,「写测试的启动成本趋近于零」,这是社区文化的一部分。

先建立图像

Rails 测试层次:

模型测试(最常用)    → 测试业务规则、验证、关联
        │
        ▼
请求测试(Controller) → 模拟 HTTP,断言响应码/重定向/DB 变化
        │
        ▼
系统测试(Capybara)  → 模拟真实浏览器点击填表单
        │
        ▼
功能/辅助测试        → helper 方法、任务执行
测试数据(Fixtures)
       │
       ▼
  test/fixtures/users.yml
  test/fixtures/articles.yml
       │
       ▼
  每条测试前自动加载到 test 数据库(每测一事务回滚)

跟着做

步骤 1:模型测试

# test/models/article_test.rb
require "test_helper"

class ArticleTest < ActiveSupport::TestCase
  test "标题必填" do
    article = Article.new(title: "", body: "正文")
    assert_not article.valid?
    assert_includes article.errors[:title], "can't be blank"
  end

  test "发布时必须有正文" do
    article = articles(:published_no_body)  # 引用 fixture
    assert_not article.valid?
    assert_includes article.errors[:body], "发布前必须填写正文"
  end

  test "标题唯一" do
    existing = articles(:one)
    duplicate = Article.new(title: existing.title, body: "正文")
    assert_not duplicate.save
    assert_includes duplicate.errors[:title], "has already been taken"
  end
end

运行:

bin/rails test test/models/article_test.rb

步骤 2:Fixture 数据准备

test/fixtures/articles.yml:

one:
  title: "Rails 概览"
  body: "这是第一篇测试文章"
  published: true
  user: alice            # 关联 fixture

two:
  title: "路由设计"
  body: "路由很重要"
  published: false
  user: bob

published_no_body:
  title: "空正文"
  body: ""
  published: true

test/fixtures/users.yml:

alice:
  name: Alice
  email: alice@example.com
  password_digest: <%= BCrypt::Password.create("secret123") %>

bob:
  name: Bob
  email: bob@example.com
  password_digest: <%= BCrypt::Password.create("secret123") %>

Fixture 支持 ERB(密码需动态哈希)。每条测试都在事务中运行,测试结束后自动回滚,数据互不污染。

步骤 3:请求测试

# test/controllers/articles_controller_test.rb
require "test_helper"

class ArticlesControllerTest < ActionDispatch::IntegrationTest
  test "未登录不能新建文章" do
    get new_article_url
    assert_redirected_to login_url
  end

  test "创建文章并重定向" do
    login_as users(:alice)   # 自定义测试辅助方法

    assert_difference "Article.count", 1 do
      post articles_url, params: { article: { title: "新文章", body: "正文正文正文" } }
    end
    assert_redirected_to article_url(Article.last)
    follow_redirect!
    assert_select "h1", "新文章"
  end
end
# test/test_helper.rb 里加辅助方法
class ActionDispatch::IntegrationTest
  def login_as(user)
    post login_url, params: { email: user.email, password: "secret123" }
  end
end

assert_difference 检查数据库计数变化;follow_redirect! 跟随重定向继续断言;assert_select 用 CSS 选择器断言 HTML 内容。

步骤 4:系统测试(浏览器级)

# test/system/articles_test.rb
require "application_system_test_case"

class ArticlesTest < ApplicationSystemTestCase
  test "新建文章完整流程" do
    login_as users(:alice)
    visit articles_url
    click_on "写新文章"

    fill_in "标题", with: "系统测试文章"
    fill_in "正文", with: "这是系统测试的正文内容,要够长。"
    click_on "保存"

    assert_text "文章创建成功"
    assert_text "系统测试文章"
  end
end
bin/rails test:system   # 跑系统测试(启动 Chrome headless)

系统测试最慢但覆盖最完整,CI 上定期跑,日常开发以模型/请求测试为主。

步骤 5:调试工具箱

日志:log/test.log、log/development.log 看每条 SQL、参数、耗时。开发模式控制台直接输 SQL。

rails console:

# sandbox 模式:自动回滚,放心造数据
bin/rails console --sandbox

# 调试完 C-x C-c 退出,数据全回滚

debug gem:代码里 debugger 或 binding.break,执行时停住:

def create
  @article = Article.new(article_params)
  debugger   # ← 断点
  @article.save
end
bin/rails server
# 请求走到断点后,终端进入调试器
# s: step in, n: next, c: continue, p 变量名: 查看

开发模式错误页面:Rails 开发环境报错时自带 Web Console 和本地变量查看,是排查问题的起点。

讲清楚要点

  1. 测试数据库完全独立:test 环境用 config/database.yml 里独立的 test 数据库,绝不会污染开发/生产数据。
  2. 事务回滚保证隔离:每测一层封装事务,测试结束自动 rollback。只有在系统测试(跨进程真实浏览器)里才用 truncation 策略。
  3. Fixtures vs Factory:Fixtures 是预定义静态数据,跑得快但维护成本高;Factory(FactoryBot gem)按需构造,测试意图更明确。Rails 默认 fixtures,项目大了可迁移。
  4. 测什么?什么不测? 测业务规则(验证、回调、权限)、测请求流(响应码、重定向、副作用)、测系统流(关键路径)。不测框架本身、不测 getter/setter、不测纯 CSS。
  5. CI 必须跑测试:bin/rails test 退出码非零即失败。GitHub Actions 或自建 CI 一行命令集成,阻止有问题的代码合入主干。

你可能会懵的地方

  • fixture 里的关联怎么写? YAML 里用 user: alice 引用其他 fixture 的 key。关联名要对应模型的 belongs_to 名(如 user 对应 belongs_to :user)。

  • assert_select 怎么选中文字? assert_select "h1", "新文章" 表示「存在 h1 元素且其文本恰好是‘新文章’」。高级用法可查 Capybara/Minitest 文档。

  • 测试跑很慢? 先看瓶颈在哪:bin/rails test --verbose 看单测耗时;系统测试最慢,可按 suite 拆分并行跑;滥用 sleep 是大忌。

  • Minitest 和 RSpec 怎么选? Rails 默认 Minitest,零配置、和框架深度集成。RSpec DSL 更丰富、生态插件多,社区偏好各占一半。初学者先用默认,团队已有 RSpec 项目再跟。

  • spring 缓存导致测试代码改了不生效? bin/spring stop 重启预加载器,或 DISABLE_SPRING=1 bin/rails test 临时禁用。

错法对照

错法问题正解
不写测试直接上线改坏功能不自知scaffold 已生成测试文件,填断言即可
测试依赖特定执行顺序fixture 数据跨测试泄露(不应发生)每条测试数据独立,用 transaction 隔离
assert true 凑覆盖率伪测试骗过自己断言具体业务结果,如 assert_redirected_to
系统测试里连数据库断言不同进程,事务隔离失效系统测试断言 UI 可见结果,不用 assert_difference
调试全靠 puts信息量少、改动代码麻烦debugger 断点 + 交互式查看变量

卡点急救

  1. bin/rails test 报 ActiveRecord::PendingMigrationError:测试前要求所有迁移执行完,跑 bin/rails db:migrate RAILS_ENV=test。
  2. 系统测试报 chromedriver 找不到:安装 Chrome for Testing 和对应 chromedriver,或改配置用 selenium-webdriver 自动下载。
  3. 测试通过但开发环境报错:环境差异(数据库版本、环境变量)——检查 .env 配置、检查 test 是否用 mock 绕过了真实逻辑。

面试常问

  • Rails 测试金字塔怎么分层? 答:单元测试(模型/辅助方法)占主体,快且稳定;请求测试覆盖控制器流转和 HTTP 契约;系统测试走真实浏览器覆盖关键业务流。比例约 70:20:10。测单元抓业务规则,测请求抓接口契约,测系统抓用户价值。

  • Fixtures 和 Factory 的优劣? 答:Fixtures 预定义、加载快、共享度高,但隐式依赖多(新同事不知道每条 fixture 关联了什么);Factory 按需创建、意图明确、可读性强,但构造慢、在重型测下可能拖慢套件。中小型项目 fixtures 足够,大型或微服务项目倾向工厂模式。

  • 怎么测需要登录的接口? 答:请求测试里写 helper 方法 login_as(user)(模拟 POST 到 login 路径存 session);或用 sign_in(若用 Devise)。关键是测试层面建立会话态,而不是测试逻辑依赖外部认证服务。

自测三问

  1. 为什么每条测试跑完后数据不会残留?
  2. 模型测试和请求测试各覆盖什么?不写请求测试只写模型测试有什么风险?
  3. debugger 停在代码里后,哪些调试命令最常用?

小结一句话

测试是刹车片不是装饰品:Minitest 开箱即用,fixtures 备数据,请求测试验流程,断点替代盲猜。

延伸

  • Testing Rails Applications(官方测试指南)
  • 下一课:部署与运维——Puma、Docker、Kamal 与 Solid Cache。
加载本节练习…
← 上一课资产与后台任务下一课 →部署与运维