rspec-best-practices
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRSpec Best Practices
RSpec 最佳实践
Use this skill when the task is to write, review, or clean up RSpec tests.
Core principle: Prefer behavioral confidence over implementation coupling. Good specs are readable, deterministic, and cheap to maintain.
当你需要编写、评审或整理RSpec测试时,可以使用本技能。
核心原则: 优先保证行为可信度,而非实现耦合。优质的测试用例应具备可读性、确定性,且易于维护。
Quick Reference
快速参考
| Aspect | Rule |
|---|---|
| Spec type | Request > controller; model for domain; system only for critical E2E |
| Assertions | Test behavior, not implementation |
| Factories | Minimal — only attributes needed for the test |
| Mocking | Stub external boundaries, not internal code |
| Isolation | Each example independent; no shared mutable state |
| Naming | |
| Service specs | Required: |
| Default to |
| External service mocking | Class methods: |
| Example names | Never use "and" in an example name — one behavior per example; split it |
| First slice | Start at the highest-value boundary that proves behavior |
| TDD | Write test first, run it, verify failure, then implement |
| 方面 | 规则 |
|---|---|
| 测试类型 | Request > controller;model用于领域逻辑;仅在关键端到端场景使用system测试 |
| 断言 | 测试行为,而非实现细节 |
| 工厂 | 仅保留测试所需的最少属性 |
| 模拟 | 对外部边界进行Stub,而非内部代码 |
| 隔离性 | 每个测试示例相互独立;无共享可变状态 |
| 命名 | |
| 服务测试 | 必填: 使用 |
| 默认使用 |
| 外部服务模拟 | 类方法:使用 |
| 测试示例名称 | 测试示例名称中绝不要使用“and” — 每个示例对应一个行为;拆分多行为示例 |
| 首个测试切片 | 从能以最少配置验证行为的最高价值边界开始 |
| TDD | 先编写测试,运行测试,验证失败,再进行实现 |
HARD-GATE: Tests Gate Implementation
硬性要求:测试是实现的前置关卡
text
THE WORKFLOW IS: PRD → TASKS → TESTS → IMPLEMENTATION
Tests are a GATE between planning and code.
NO implementation code may be written until:
1. The test EXISTS
2. The test has been RUN
3. The test FAILS for the correct reason (feature missing, not typo)Write code before the test? Delete it. Start over.
The gate cycle for each behavior:
- Write test: One minimal test showing what the behavior should do
- Run test: Execute it — this is mandatory, not optional
- Validate failure: Confirm it fails because the feature is missing
- CHECKPOINT — Test Design Review: Present the failing test. Confirm boundary, behavior, and edge cases before writing implementation. See for checkpoint format.
rails-tdd-slices - GATE PASSED — you may now write implementation code
- CHECKPOINT — Implementation Proposal: Before writing code, state which classes/methods will be created or changed and the rough structure. Wait for confirmation.
- Write minimal code: Simplest implementation to make the test pass
- Run test again: Confirm it passes and no other tests break
- Refactor: Clean up — tests must stay green
- Next behavior: Return to step 1
text
工作流程为:PRD → 任务 → 测试 → 实现
测试是规划与代码之间的关卡。
在满足以下条件前,不得编写任何实现代码:
1. 测试已存在
2. 测试已运行
3. 测试因正确原因失败(功能缺失,而非拼写错误)先写代码再写测试?删除代码,重新开始。
每个行为的关卡循环:
- 编写测试: 编写一个最小化的测试,明确该行为应实现的功能
- 运行测试: 执行测试 — 这是强制要求,而非可选步骤
- 验证失败: 确认测试因功能缺失而失败
- 检查点 — 测试设计评审: 展示失败的测试。在编写实现前,确认边界、行为和边缘案例。查看了解检查点格式。
rails-tdd-slices - 通过关卡 — 你现在可以编写实现代码了
- 检查点 — 实现方案: 编写代码前,说明将创建或修改的类/方法以及大致结构。等待确认。
- 编写最小化代码: 用最简单的实现让测试通过
- 再次运行测试: 确认测试通过且未破坏其他测试
- 重构: 清理代码 — 测试必须保持通过状态
- 下一个行为: 返回步骤1
TDD Slice Selection
TDD 测试切片选择
Choose the first failing spec at the boundary that gives the strongest signal with the least setup:
| Change type | Best first spec |
|---|---|
| New endpoint, controller action, or API behavior | Request spec |
| New domain rule on an existing model | Model spec |
| New service object or orchestration flow | Service spec |
| Background job behavior | Job spec; add service/domain spec if logic is non-trivial |
| Rails engine route, install, or generator behavior | Engine request/routing/generator spec via |
| Bug fix | Reproduction spec at the boundary where the bug is observed |
选择能以最少配置提供最强验证信号的边界作为首个失败测试:
| 变更类型 | 最优首个测试 |
|---|---|
| 新端点、控制器动作或API行为 | Request测试 |
| 现有模型的新领域规则 | Model测试 |
| 新服务对象或编排流程 | Service测试 |
| 后台任务行为 | Job测试;若逻辑复杂则补充service/领域测试 |
| Rails引擎路由、安装或生成器行为 | 通过 |
| Bug修复 | 在观察到Bug的边界编写复现测试 |
Structure and Style
结构与风格
- describe for the class, module, or behavior; context for scenarios ("when valid", "when user is missing").
- Mirror source paths under (e.g.
spec/→app/models/user.rb).spec/models/user_spec.rb - Use shared_examples / shared_context for repeated behavior; put reusable shared examples under .
spec/support/ - Use only when
let_it_bealready exists in the project.test-prof - Time-dependent behavior MUST use — do not set dates in the past as a shortcut, do not stub
travel_to. Wrap assertions in aTime.nowblock to control the clock:travel_to
ruby
let(:subscription) { create(:subscription, activated_at: Time.current) }
context 'after expiration' do
it 'is expired' do
travel_to 31.days.from_now do
expect(subscription).to be_expired
end
end
endMinimal request spec skeleton:
ruby
undefined- describe 用于类、模块或行为;context 用于场景(如"when valid"、"when user is missing")。
- 在目录下镜像源码路径(例如
spec/→app/models/user.rb)。spec/models/user_spec.rb - 对重复行为使用shared_examples / shared_context;将可复用的共享示例放在目录下。
spec/support/ - 仅当项目中已存在时,才使用
test-prof。let_it_be - 时间相关行为必须使用— 不要通过设置过去的日期走捷径,不要Stub
travel_to。将断言包裹在Time.now块中以控制时间:travel_to
ruby
let(:subscription) { create(:subscription, activated_at: Time.current) }
context 'after expiration' do
it 'is expired' do
travel_to 31.days.from_now do
expect(subscription).to be_expired
end
end
end最小化Request测试骨架:
ruby
undefinedfrozen_string_literal: true
frozen_string_literal: true
RSpec.describe 'POST /orders', type: :request do
let(:product) { create(:product, stock: 5) }
context 'when product is in stock' do
it 'returns 201 for an in-stock product' do
post orders_path, params: { order: { product_id: product.id } }, as: :json
expect(response).to have_http_status(:created)
end
end
end
**Monolith vs engine:** When the project is a Rails engine, use `rails-engine-testing` for dummy-app setup and engine request/routing/generator specs; keep using this skill for general RSpec style.
For more examples (model spec, service spec, shared_examples, travel_to), see [EXAMPLES.md](./EXAMPLES.md).RSpec.describe 'POST /orders', type: :request do
let(:product) { create(:product, stock: 5) }
context 'when product is in stock' do
it 'returns 201 for an in-stock product' do
post orders_path, params: { order: { product_id: product.id } }, as: :json
expect(response).to have_http_status(:created)
end
end
end
**单体应用 vs 引擎:** 若项目是Rails引擎,使用`rails-engine-testing`进行dummy-app配置和引擎request/路由/生成器测试;通用RSpec风格仍遵循本技能规范。
更多示例(model测试、service测试、shared_examples、travel_to)请参见 [EXAMPLES.md](./EXAMPLES.md)。Pitfalls
常见陷阱
| Pitfall | What to do |
|---|---|
| Starting with the lowest layer by habit | Begin at the boundary that proves the behavior users care about |
| Testing mock behavior instead of real behavior | Assert outcomes, not implementation details |
Recommending | Only use it when |
| Factories creating large graphs by default | Minimal factories — only what the test needs |
Setting dates in the past instead of | Always use |
| Code written before the test | Delete it. Reproduction step isn't done yet. |
| Test name contains "and" | One behavior per example. Split it. |
| 陷阱 | 解决方法 |
|---|---|
| 习惯性从最底层开始 | 从用户关心的行为边界开始 |
| 测试模拟行为而非真实行为 | 断言结果,而非实现细节 |
推荐在所有仓库使用 | 仅当项目中已存在 |
| 工厂默认生成大量关联对象 | 保持工厂最小化 — 仅保留测试所需内容 |
设置过去的日期而非使用 | 时间相关断言始终使用 |
| 先写代码再写测试 | 删除代码。复现步骤尚未完成。 |
| 测试名称包含"and" | 每个示例对应一个行为。拆分多行为示例。 |
Integration
集成
| Skill | When to chain |
|---|---|
| rails-tdd-slices | When the hardest part is choosing the first failing Rails spec or vertical slice |
| rails-bug-triage | When a bug report must be turned into a reproducible failing spec and fix plan |
| rspec-service-testing | For service object specs — |
| rails-engine-testing | For engine specs — dummy app, routing specs, generator specs |
| rails-code-review | When reviewing test quality as part of code review |
| refactor-safely | When adding characterization tests before refactoring |
| 技能 | 何时关联使用 |
|---|---|
| rails-tdd-slices | 当难以选择首个失败的Rails测试或垂直切片时 |
| rails-bug-triage | 当需要将Bug报告转化为可复现的失败测试和修复方案时 |
| rspec-service-testing | 用于服务对象测试 — 对注入的实例协作对象使用 |
| rails-engine-testing | 用于引擎测试 — dummy app、路由测试、生成器测试 |
| rails-code-review | 当在代码评审中检查测试质量时 |
| refactor-safely | 当在重构前添加特征测试时 |