rails-tdd-slices
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRails TDD Slices
Rails TDD 切片
Use this skill when the hardest part of the task is deciding where TDD should start.
Core principle: Start at the highest-value boundary that proves the behavior with the least unnecessary setup.
当任务中最困难的部分是决定TDD应从何处开始时,使用本技能。
核心原则: 从能以最少不必要配置验证行为的最高价值边界开始。
Quick Reference
快速参考
| Change type | First spec | Path | Why |
|---|---|---|---|
| API contract, params, status code, JSON shape | Request spec | | Proves the real HTTP contract |
| Domain rule on a cohesive record or value object | Model spec | | Fast feedback on domain behavior |
| Multi-step orchestration across collaborators | Service spec | | Focuses on the workflow boundary |
| Enqueue/run/retry/discard behavior | Job spec | | Captures async semantics directly |
| Critical Turbo/Stimulus or browser-visible flow | System spec | | Use only when browser interaction is the real risk |
| Engine routing, generators, host integration | Engine spec | | Normal app specs miss engine wiring — see |
| Bug fix | Reproduction spec | Where the bug is observed | Proves the fix and prevents regression |
| Unsure between layers | Higher boundary first | — | Easier to prove real behavior before drilling down |
| 变更类型 | 首选测试用例 | 路径 | 原因 |
|---|---|---|---|
| API契约、参数、状态码、JSON结构 | Request spec | | 验证真实的HTTP契约 |
| 内聚记录或值对象的领域规则 | Model spec | | 快速反馈领域行为 |
| 跨协作对象的多步骤编排 | Service spec | | 聚焦工作流边界 |
| 入队/运行/重试/丢弃行为 | Job spec | | 直接捕获异步语义 |
| 关键Turbo/Stimulus或浏览器可见流程 | System spec | | 仅当浏览器交互是真正风险时使用 |
| 引擎路由、生成器、宿主集成 | Engine spec | | 常规应用测试会遗漏引擎连接配置——参见 |
| Bug修复 | 复现测试用例 | Bug出现的位置 | 验证修复效果并防止回归 |
| 不确定选择哪一层 | 优先选择更高层级的边界 | — | 在深入细节前,更容易验证真实行为 |
HARD-GATE
严格规则
text
DO NOT choose the first spec based on convenience alone.
DO NOT start with a lower-level unit if the real risk is request, job, engine, or persistence wiring.
ALWAYS run the chosen spec and verify it fails for the right reason before implementation.text
DO NOT choose the first spec based on convenience alone.
DO NOT start with a lower-level unit if the real risk is request, job, engine, or persistence wiring.
ALWAYS run the chosen spec and verify it fails for the right reason before implementation.Process
流程
- Name the behavior: State the user-visible outcome or invariant to prove.
- Locate the boundary: Decide where the behavior is observed first: HTTP request, service entry point, model rule, job execution, engine integration, or external adapter.
- Pick the smallest strong slice: Choose the spec type that proves the behavior without dragging in unrelated layers.
- Suggest the path: Name the likely spec path using normal Rails conventions (for example ,
spec/requests/...,spec/services/...,spec/jobs/...).spec/models/... - Write one failing example: Keep it minimal; one example is enough to open the gate.
- Run and validate: Confirm the failure is because the behavior is missing, not because the setup is broken.
- Hand off: Continue with ,
rspec-best-practices,rspec-service-testing, or the implementation skill that fits the slice.rails-engine-testing
- 定义行为: 明确要验证的用户可见结果或不变量。
- 定位边界: 确定行为首次被观察到的位置:HTTP请求、服务入口点、模型规则、任务执行、引擎集成或外部适配器。
- 选择最小有效切片: 选择无需引入无关层即可验证行为的测试用例类型。
- 建议路径: 使用标准Rails约定指定可能的测试路径(例如、
spec/requests/...、spec/services/...、spec/jobs/...)。spec/models/... - 编写一个失败的测试示例: 保持最简;一个示例足以开启TDD流程。
- 运行并验证: 确认失败原因是行为缺失,而非配置错误。
- 交接: 继续使用、
rspec-best-practices、rspec-service-testing或适合该切片的实现技能。rails-engine-testing
Examples
示例
Good: New JSON Endpoint
良好实践:新增JSON接口
ruby
undefinedruby
undefinedBehavior: POST /orders validates params and returns 201 with JSON payload
Behavior: POST /orders validates params and returns 201 with JSON payload
First slice: request spec
First slice: request spec
Suggested path: spec/requests/orders/create_spec.rb
Suggested path: spec/requests/orders/create_spec.rb
RSpec.describe "POST /orders", type: :request do
let(:user) { create(:user) }
let(:valid_params) { { order: { product_id: create(:product).id, quantity: 1 } } }
before { sign_in user }
it "creates an order and returns 201" do
post orders_path, params: valid_params, as: :json
expect(response).to have_http_status(:created)
expect(response.parsed_body["id"]).to be_present
end
end
undefinedRSpec.describe "POST /orders", type: :request do
let(:user) { create(:user) }
let(:valid_params) { { order: { product_id: create(:product).id, quantity: 1 } } }
before { sign_in user }
it "creates an order and returns 201" do
post orders_path, params: valid_params, as: :json
expect(response).to have_http_status(:created)
expect(response.parsed_body["id"]).to be_present
end
end
undefinedGood: New Orchestration Service
良好实践:新增编排服务
ruby
undefinedruby
undefinedBehavior: Orders::CreateOrder validates inventory, persists, and enqueues follow-up work
Behavior: Orders::CreateOrder validates inventory, persists, and enqueues follow-up work
First slice: service spec
First slice: service spec
Suggested path: spec/services/orders/create_order_spec.rb
Suggested path: spec/services/orders/create_order_spec.rb
RSpec.describe Orders::CreateOrder do
subject(:result) { described_class.call(user: user, product: product, quantity: 1) }
let(:user) { create(:user) }
let(:product) { create(:product, stock: 5) }
it "returns a successful result with the new order" do
expect(result).to be_success
expect(result.order).to be_persisted
end
end
undefinedRSpec.describe Orders::CreateOrder do
subject(:result) { described_class.call(user: user, product: product, quantity: 1) }
let(:user) { create(:user) }
let(:product) { create(:product, stock: 5) }
it "returns a successful result with the new order" do
expect(result).to be_success
expect(result.order).to be_persisted
end
end
undefinedTest Feedback Checkpoint
测试反馈检查点
After writing and running the first failing spec, pause before implementation and present the test for review:
CHECKPOINT: Test Design Review
1. Present: Show the failing spec(s) written
2. Ask:
- Does this test cover the right behavior?
- Is the boundary correct (request vs service vs model)?
- Are the most important edge cases represented?
- Is the failure reason correct (feature missing, not setup error)?
3. Confirm: Only proceed to implementation once test design is approved.Why this matters: Implementing against a poorly designed test wastes the TDD cycle. A 2-minute review of the test now prevents a full rewrite later.
Hand off: After test design is confirmed → for the full TDD gate cycle.
rspec-best-practices在编写并运行首个失败测试用例后,在开始实现前暂停,提交测试供评审:
CHECKPOINT: Test Design Review
1. Present: Show the failing spec(s) written
2. Ask:
- Does this test cover the right behavior?
- Is the boundary correct (request vs service vs model)?
- Are the most important edge cases represented?
- Is the failure reason correct (feature missing, not setup error)?
3. Confirm: Only proceed to implementation once test design is approved.为什么这很重要: 针对设计不佳的测试进行实现会浪费TDD周期。现在花2分钟评审测试,可避免后续全面重写。
交接: 测试设计确认后 → 使用完成完整的TDD流程。
rspec-best-practicesPitfalls
常见陷阱
| Pitfall | What to do |
|---|---|
| Starting with a PORO spec because it is easy | Easy ≠ high-signal — choose the boundary that proves the real behavior |
| Writing three spec types before running any | Pick one slice, run it, prove the failure, then proceed |
| Defaulting to request specs for everything | Some domain rules are better proven at the model or service layer |
| Defaulting to model specs for controller behavior | Controllers and APIs need request-level proof |
| Using controller specs as the default HTTP entry point | Prefer request specs unless the repo has an existing reason |
| Jumping to system specs too early | Reserve for critical browser flows that lower layers cannot prove |
| "We'll add the request spec later" | The spec is the gate — implement only after the first slice is failing for the right reason |
| First spec requires excessive factory setup | Excessive setup = wrong boundary. Simplify or move the slice. |
| 陷阱 | 应对方法 |
|---|---|
| 因为简单就从PORO测试开始 | 简单 ≠ 高价值信号 —— 选择能验证真实行为的边界 |
| 在运行任何测试前编写三种类型的测试用例 | 选择一个切片,运行它,验证失败原因,再继续 |
| 所有场景都默认使用Request spec | 某些领域规则在模型或服务层验证效果更好 |
| 针对控制器行为默认使用Model spec | 控制器和API需要请求级别的验证 |
| 将Controller spec作为默认的HTTP入口测试 | 除非仓库有既定原因,否则优先使用Request spec |
| 过早使用System spec | 仅保留用于低层无法验证的关键浏览器流程 |
| "我们稍后再添加Request spec" | 测试是TDD的入口 —— 仅当首个切片因正确原因失败后再开始实现 |
| 首个测试用例需要过多的工厂配置 | 过多配置 = 边界选择错误。简化或更换切片。 |
Integration
集成
| Skill | When to chain |
|---|---|
| rspec-best-practices | After choosing the first slice, to enforce the TDD loop correctly |
| rspec-service-testing | When the first slice is a service object spec |
| rails-engine-testing | When the first slice belongs to an engine |
| rails-bug-triage | When the starting point is an existing bug report |
| refactor-safely | When the task is mostly structural and needs characterization tests first |
| 技能 | 何时衔接 |
|---|---|
| rspec-best-practices | 选择首个切片后,用于正确执行TDD循环 |
| rspec-service-testing | 当首个切片是服务对象测试用例时 |
| rails-engine-testing | 当首个切片属于引擎时 |
| rails-bug-triage | 当起点是现有Bug报告时 |
| refactor-safely | 当任务主要是结构调整且需要先进行特征测试时 |