mcpField
T
testing

testing

github.com/thinkoodle测试与质量7179 次安装8 周前更新
代码仓库

关于 testing

面向 Rails 开发者的测试技能,帮助规划测试类型、编写测试并处理并行执行、CI 和不稳定测试。内容强调测试金字塔、优先使用请求测试以及使用 fixtures。

主要功能

  1. 01选择合适的 Rails 测试类型
  2. 02编写请求与系统测试
  3. 03配置并行测试和 CI
  4. 04排查数据库与不稳定测试
  5. 05使用 fixtures 组织测试数据

适用场景

  1. 01为 Rails 应用建立测试套件
  2. 02为 HTTP 端点添加请求测试
  3. 03配置并行测试或 CI 流程
  4. 04定位系统测试中的波动

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/thinkoodle/rails-skills --skill testing

安装后在 Claude Code 中输入/testing确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
ci.md
fixtures.md
helpers-and-assertions.md
parallel-testing.md
request-tests.md
system-tests.md

详细介绍

Rails 测试专家

为 Rails 应用构建快速、可靠、易维护的测试套件。本技能涵盖 Rails 测试生态系统——测试类型、基础设施,以及应在何处测试哪些内容。

**配套技能:**有关 Minitest 专用断言、fixtures 深入解析、TDD 工作流和 RSpec 到 Minitest 的转换,请参阅 minitest 技能。

何时使用本技能

  • 设置或配置 Rails 测试套件
  • 在不同测试类型之间进行选择(系统、请求、模型、任务、邮件程序等)
  • 使用 Capybara 编写系统测试
  • 配置并行测试或 CI 流水线
  • 调试不稳定的测试或测试数据库问题
  • 为新功能添加测试覆盖

理念

坚守测试金字塔
      /\       System Tests (FEW — critical user paths only)
     /  \
    /____\     Request/Integration Tests (MODERATE)
   /      \
  /________\   Unit Tests (MANY — models, services, policies, jobs)

**大多数 Agent 的错误都源于颠倒这个金字塔。**为所有内容编写系统测试是头号错误。系统测试比单元测试慢 10–100 倍。一个不稳定的系统测试所浪费的时间,超过 50 个模型测试所浪费的时间。

核心规则
  1. 测试行为,而非实现——断言结果,而不是方法调用
  2. 使用 fixtures,而非工厂——快 10–100 倍,且不会级联创建
  3. 优先使用请求测试,而非系统测试——仅在依赖 JS 的流程中使用系统测试
  4. 每项测试都相互独立——没有共享状态,也不依赖测试顺序
  5. 快速反馈 > 全面覆盖——经常运行的快速测试套件,胜过不会运行的慢速套件

选择合适的测试类型

这是最重要的决定。选错会浪费时间编写缓慢、脆弱的测试。

测试类型基类速度用途
模型ActiveSupport::TestCase⚡ 快验证、作用域、业务逻辑、回调
服务/POROActiveSupport::TestCase⚡ 快服务对象、表单对象、查询对象
任务ActiveJob::TestCase⚡ 快任务行为、入队、重试
邮件程序ActionMailer::TestCase⚡ 快邮件内容、收件人、附件
辅助方法ActionView::TestCase⚡ 快视图辅助方法的输出
请求ActionDispatch::IntegrationTest🔶 中HTTP 请求/响应周期、API 端点
集成ActionDispatch::IntegrationTest🔶 中无需浏览器的多步工作流
通道ActionCable::Channel::TestCase⚡ 快通道订阅、流
连接ActionCable::Connection::TestCase⚡ 快WebSocket 身份验证、连接标识符
系统ActionDispatch::SystemTestCase🐌 慢JS 交互、关键端到端用户路径
决策树
Does it test business logic with no HTTP needed?
  → Model / Service test

Does it test an HTTP endpoint's response?
  → Request test

Does it require a real browser (JS, complex UI)?
  → System test (keep these minimal)

Does it test email content or delivery?
  → Mailer test

Does it test a background job?
  → Job test

Does it test a view helper method?
  → Helper test

Does it test a WebSocket channel?
  → Channel test

测试目录结构

test/
├── test_helper.rb                    # Global config
├── application_system_test_case.rb   # System test base (created on first system test)
├── controllers/                      # Controller/functional tests (legacy — prefer request tests)
├── fixtures/                         # YAML fixture data
│   ├── users.yml
│   ├── files/                        # File fixtures for uploads
│   └── action_text/rich_texts.yml    # Action Text fixtures
├── helpers/                          # View helper tests
├── integration/                      # Integration tests
├── jobs/                             # Job tests
├── mailers/                          # Mailer tests
│   └── previews/                     # Mailer previews (not tests, but useful)
├── models/                           # Model tests (bulk of your tests)
├── channels/                         # Action Cable channel tests
│   └── application_cable/            # Connection tests
├── requests/                         # Request tests (preferred over controllers/)
├── services/                         # Service object tests
├── system/                           # System tests (keep small)
└── support/                          # Shared test helpers

测试运行器

必备命令
# Run everything (excludes system tests)
bin/rails test

# Run system tests separately
bin/rails test:system

# Run ALL tests including system
bin/rails test:all

# Run a single file
bin/rails test test/models/user_test.rb

# Run a specific test by line number
bin/rails test test/models/user_test.rb:42

# Run a line range
bin/rails test test/models/user_test.rb:10-30

# Run by name pattern
bin/rails test -n /password/

# Run a directory
bin/rails test test/models/

# Fail fast — stop on first failure
bin/rails test --fail-fast

# Verbose output
bin/rails test -v

# Profile slow tests
bin/rails test --profile

# Set seed for reproducibility
bin/rails test --seed 12345
重要标志
标志用途
-n PATTERN按名称筛选测试(字符串或 /regex/)
-f / --fail-fast在首次失败时停止
-v / --verbose显示每项测试的名称
-b / --backtrace完整回溯(而不只是应用代码行)
--profile [N]列出最慢的 N 项测试(默认 10)
-p / --pride彩虹输出 🌈
-S / --skip CODES跳过报告类型(例如,E 表示错误)

请求测试(HTTP 测试的首选方式)

请求测试会通过中间件栈模拟完整的 HTTP 请求。新代码请使用请求测试,而不是控制器测试。

class ArticlesRequestTest < ActionDispatch::IntegrationTest
  setup do
    @user = users(:admin)
    @article = articles(:published)
  end

  test "GET /articles returns success" do
    get articles_url
    assert_response :success
  end

  test "POST /articles creates with valid params" do
    sign_in @user
    assert_difference "Article.count", 1 do
      post articles_url, params: { article: { title: "New", body: "Content" } }
    end
    assert_redirected_to article_url(Article.last)
  end

  test "POST /articles rejects invalid params" do
    sign_in @user
    assert_no_difference "Article.count" do
      post articles_url, params: { article: { title: "" } }
    end
    assert_response :unprocessable_entity
  end
end
请求后可用的内容
  • response / response.body / response.parsed_body
  • cookies、flash、session
  • @controller、@request、@response

参阅 references/request-tests.md,了解 JSON API 测试、Turbo Stream 测试、文件上传、身份验证模式和 DOM 断言。

系统测试(谨慎使用)

系统测试通过 Capybara 在真实浏览器中运行。仅用于需要 JavaScript 或复杂 UI 交互的关键路径。

何时编写系统测试

✅ 用户注册/登录流程 ✅ 结账/支付流程 ✅ 复杂的 JS 交互(拖放、模态框、实时搜索) ✅ 依赖 Turbo/Stimulus 的功能 ✅ 带预览的文件上传

何时不编写系统测试

❌ CRUD 操作(使用请求测试) ❌ 验证错误的显示(使用请求测试 + assert_select) ❌ API 端点(使用请求测试) ❌ 无需浏览器即可测试的任何内容

配置
# test/application_system_test_case.rb
require "test_helper"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :headless_chrome
end

驱动选项:

驱动浏览器使用场景
:selenium + :headless_chromeChrome(无头)默认选项,适合 CI
:selenium + :headless_firefoxFirefox(无头)替代选项
:selenium + :chromeChrome(可见)调试
:cuprite通过 CDP 使用 Chrome更快,无 Selenium 开销
编写系统测试
require "application_system_test_case"

class CheckoutTest < ApplicationSystemTestCase
  test "user completes checkout" do
    user = users(:buyer)
    product = products(:widget)

    visit new_session_path
    fill_in "Email", with: user.email
    fill_in "Password", with: "password"
    click_on "Sign in"

    visit product_path(product)
    click_on "Add to Cart"
    click_on "Checkout"

    fill_in "Card number", with: "4242424242424242"
    click_on "Pay"

    assert_text "Order confirmed"
  end
end
避免不稳定的系统测试
  • assert_text、assert_selector 会自动等待——请使用它们,而不是 page.has_css?
  • 如果仍不稳定,请添加显式等待:assert_text "Done", wait: 5
  • 切勿使用 sleep——始终使用 Capybara 会等待的断言
  • 使用 take_screenshot 进行调试;失败的测试会自动截图并保存到 tmp/screenshots/

参见 references/system-tests.md,了解 Capybara DSL 参考、Cuprite 设置、JS 交互模式以及 Turbo/Stimulus 测试。

邮件程序测试

class UserMailerTest < ActionMailer::TestCase
  test "welcome email" do
    email = UserMailer.welcome(users(:new_user))
    assert_emails 1 do
      email.deliver_now
    end
    assert_equal ["[email protected]"], email.from
    assert_equal [users(:new_user).email], email.to
    assert_equal "Welcome!", email.subject
    assert_match users(:new_user).name, email.body.to_s
  end
end

来自请求测试: assert_emails 1 { post users_url, params: {...} } 已加入队列: assert_enqueued_email_with UserMailer, :welcome, args: [user] { ... }

参见 references/helpers-and-assertions.md,了解多部分邮件、附件、参数化邮件程序和预览。

作业测试

class ProcessOrderJobTest < ActiveJob::TestCase
  test "charges the account" do
    perform_enqueued_jobs do
      ProcessOrderJob.perform_later(orders(:pending))
    end
    assert orders(:pending).reload.charged?
  end
end

验证从其他代码中加入队列的情况:

class OrderTest < ActiveSupport::TestCase
  include ActiveJob::TestHelper

  test "#place enqueues processing job" do
    assert_enqueued_with(job: ProcessOrderJob) do
      orders(:new).place!
    end
  end
end

参见 references/helpers-and-assertions.md,了解作业队列断言、筛选、链式作业和异常测试。

辅助方法测试

class CurrencyHelperTest < ActionView::TestCase
  test "formats USD" do
    assert_equal "$10.00", format_price(1000, "USD")
  end

  test "handles zero" do
    assert_equal "$0.00", format_price(0, "USD")
  end
end

Action Cable 测试

# Channel test
class ChatChannelTest < ActionCable::Channel::TestCase
  test "subscribes to room stream" do
    subscribe room: "general"
    assert subscription.confirmed?
    assert_has_stream "chat_general"
  end
end

# Connection test
class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
  test "connects with valid user" do
    cookies.signed[:user_id] = users(:admin).id
    connect
    assert_equal users(:admin).id, connection.current_user.id
  end

  test "rejects without auth" do
    assert_reject_connection { connect }
  end
end

广播断言(可用于任何测试): include ActionCable::TestHelper,然后使用 assert_broadcast_on、assert_broadcasts。

参见 references/helpers-and-assertions.md,了解流断言、自定义频道方法以及从模型测试广播。

测试数据库管理

架构同步
# After migrations, sync test DB schema
bin/rails db:test:prepare

# Or recreate from scratch
bin/rails db:test:purge db:test:prepare

# Rails auto-checks for pending migrations before tests run
事务

默认情况下,每个测试都在数据库事务中运行,并在完成后回滚。这样无需手动清理即可隔离测试。

必要时禁用(例如,测试多线程代码时):

class ThreadedTest < ActiveSupport::TestCase
  self.use_transactional_tests = false

  teardown do
    # Manual cleanup needed!
    User.delete_all
  end
end

并行测试

基于进程(默认)
# test/test_helper.rb
class ActiveSupport::TestCase
  parallelize(workers: :number_of_processors)
end

为每个工作进程创建单独的测试数据库(test-database-0、test-database-1 等)。

# Override worker count
PARALLEL_WORKERS=4 bin/rails test
基于线程
class ActiveSupport::TestCase
  parallelize(workers: :number_of_processors, with: :threads)
end

适用于 JRuby/TruffleRuby,或进程分叉开销较大的情况。

使用 parallelize_setup/parallelize_teardown 钩子进行每个工作进程的设置。默认阈值为 50 个测试(低于该数量时不会并行执行)。使用 config.active_support.test_parallelization_threshold = 100 自定义阈值。

参见 references/parallel-testing.md,了解进程与线程的对比、钩子以及常见的并行测试问题。

CI 配置

# config/environments/test.rb
config.eager_load = ENV["CI"].present?
# CI steps
bin/rails db:test:prepare
bin/rails test                    # Unit + request tests
bin/rails test:system             # System tests (separate job recommended)

CI 提示: 使用 PARALLEL_WORKERS 并行执行,将系统测试拆分到单独的 CI 作业中,缓存 gems/node_modules,使用无头 Chrome,并添加 Zeitwerk 急切加载测试。

参见 references/ci.md,获取完整的 GitHub Actions YAML 和 CI 模式。

时间相关测试

freeze_time do ... end                                    # Freeze at current moment
travel_to Time.zone.local(2024, 1, 15) do ... end        # Travel to specific time
travel 3.days do ... end                                  # Travel forward
travel_back                                               # Return to real time

参见 references/parallel-testing.md,了解详细的时间测试模式、常见陷阱和不稳定测试的修复方法。

应避免的反模式

  1. 系统测试过多——如果不需要浏览器,就使用请求测试
  2. 测试 Rails 本身——不要测试 validates :name, presence: true 是否有效;要测试你自己的业务逻辑
  3. 测试实现细节——assert_received(:method) 会让测试依赖内部实现
  4. 工厂级联——Factory Bot 为一个测试创建 15 条记录;请使用 fixtures
  5. 共享可变状态——在一个测试中设置的实例变量泄漏到另一个测试中
  6. 基于休眠的等待——系统测试中使用 sleep 2;请使用 Capybara 内置的等待机制
  7. 精确时间戳断言——assert_equal Time.current, record.created_at 容易导致测试不稳定
  8. 测试私有方法——只测试公共接口
  9. 过度模拟——模拟所有内容意味着你测试的是模拟对象,而不是代码
  10. 没有负向测试——始终测试哪些情况应该失败或被拒绝

快速参考:该在哪里测试什么

内容测试类型示例
模型验证模型测试refute User.new(email: nil).valid?
模型作用域模型测试assert_includes User.active, @user
业务逻辑方法模型/服务测试assert_equal 42, order.total
API 端点请求测试get api_users_url, as: :json
页面正确渲染请求测试get root_url; assert_response :success
表单提交请求测试post articles_url, params: {...}
身份验证/授权请求测试get admin_url; assert_response :redirect
邮件内容邮件程序测试assert_equal "Welcome", email.subject
邮件发送请求测试assert_emails 1 { post ... }
作业行为作业测试perform_enqueued_jobs { MyJob.perform_later }
作业入队模型/请求测试assert_enqueued_with(job: MyJob) { ... }
依赖 JS 的流程系统测试click_on "Submit"; assert_text "Done"
视图辅助方法辅助方法测试assert_equal "$10", format_price(10)
WebSocket 频道频道测试subscribe; assert_has_stream "room_1"

评论

支持 Markdown