rspec-coder
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRSpec Coder
RSpec 测试编码规范
Core Philosophy
核心理念
- AAA Pattern: Arrange-Act-Assert structure for clarity
- Behavior over Implementation: Test what code does, not how
- Isolation: Tests should be independent
- Descriptive Names: Blocks should clearly explain behavior
- Coverage: Test happy paths AND edge cases
- Fast Tests: Minimize database operations
- Fixtures: Use fixtures for common data setup
- Shoulda Matchers: Use for validations and associations
- AAA Pattern:采用Arrange-Act-Assert结构,保证测试清晰性
- 行为优先于实现:测试代码的功能,而非实现细节
- 独立性:测试用例之间应相互独立
- 命名描述性:代码块需清晰说明测试行为
- 覆盖全面:同时测试正常流程和边缘场景
- 测试高效:尽量减少数据库操作
- Fixtures:使用fixtures进行通用数据准备
- Shoulda Matchers:用于验证模型校验规则和关联关系
Critical Conventions
关键约定
❌ Don't Add require 'rails_helper'
require 'rails_helper'❌ 不要添加require 'rails_helper'
require 'rails_helper'RSpec imports via config. Adding manually is redundant.
.rspecruby
undefinedRSpec会通过配置文件自动导入,手动添加属于冗余操作。
.rspecruby
undefined✅ GOOD - no require needed
✅ 正确写法 - 无需添加require
RSpec.describe User do
...
end
undefinedRSpec.describe User do
...
end
undefined❌ Don't Add Redundant Spec Type
❌ 不要添加冗余的Spec类型
RSpec infers type from file location automatically.
ruby
undefinedRSpec会根据文件位置自动推断类型。
ruby
undefined✅ GOOD - type inferred from spec/models/ location
✅ 正确写法 - 类型由spec/models/目录自动推断
RSpec.describe User do
...
end
undefinedRSpec.describe User do
...
end
undefined✅ Use Namespace WITHOUT Leading ::
::✅ 使用命名空间时不要加前置::
::ruby
undefinedruby
undefined✅ GOOD - no leading double colons
✅ 正确写法 - 不要加前置双冒号
RSpec.describe DynamicsGp::ERPSynchronizer do
...
end
undefinedRSpec.describe DynamicsGp::ERPSynchronizer do
...
end
undefinedTest Organization
测试组织
File Structure
文件结构
- - Model unit tests
spec/models/ - - Service object tests
spec/services/ - - Controller tests
spec/controllers/ - - Request specs (API testing)
spec/requests/ - - Mailer tests
spec/mailers/ - - Background job tests
spec/jobs/ - - Test data
spec/fixtures/ - - Helper modules and shared examples
spec/support/ - - Rails-specific configuration
spec/rails_helper.rb
- - 模型单元测试
spec/models/ - - 服务对象测试
spec/services/ - - 控制器测试
spec/controllers/ - - 请求测试(API测试)
spec/requests/ - - 邮件器测试
spec/mailers/ - - 后台任务测试
spec/jobs/ - - 测试数据
spec/fixtures/ - - 辅助模块和共享示例
spec/support/ - - Rails专属配置文件
spec/rails_helper.rb
Using describe
and context
describecontext使用describe
和context
describecontext| Block | Purpose | Example |
|---|---|---|
| Groups by method/class | |
| Groups by condition | |
ruby
RSpec.describe OrderProcessor do
describe "#process" do
context "with valid payment" do
# success tests
end
context "with invalid payment" do
# failure tests
end
end
end| 代码块 | 用途 | 示例 |
|---|---|---|
| 按方法/类分组 | |
| 按条件场景分组 | |
ruby
RSpec.describe OrderProcessor do
describe "#process" do
context "with valid payment" do
# 成功场景测试
end
context "with invalid payment" do
# 失败场景测试
end
end
endSubject and Let
Subject与Let
See references/patterns.md for detailed examples.
| Pattern | Use Case |
|---|---|
| Primary object/method under test |
| Lazy-evaluated, memoized data |
| Eager evaluation (before each test) |
ruby
RSpec.describe User do
describe "#full_name" do
subject(:full_name) { user.full_name }
let(:user) { users(:alice) }
it { is_expected.to eq("Alice Smith") }
end
end详细示例请参考references/patterns.md。
| 模式 | 使用场景 |
|---|---|
| 测试的核心对象/方法 |
| 延迟求值、可缓存的数据 |
| 立即求值(在每个测试前执行) |
ruby
RSpec.describe User do
describe "#full_name" do
subject(:full_name) { user.full_name }
let(:user) { users(:alice) }
it { is_expected.to eq("Alice Smith") }
end
endFixtures
Fixtures
See references/patterns.md for detailed examples.
yaml
undefined详细示例请参考references/patterns.md。
yaml
undefinedspec/fixtures/users.yml
spec/fixtures/users.yml
alice:
name: Alice Smith
email: alice@example.com
admin: false
```ruby
RSpec.describe User do
fixtures :users
it "validates email" do
expect(users(:alice)).to be_valid
end
endalice:
name: Alice Smith
email: alice@example.com
admin: false
```ruby
RSpec.describe User do
fixtures :users
it "validates email" do
expect(users(:alice)).to be_valid
end
endMocking and Stubbing
Mocking与Stubbing
See references/patterns.md for detailed examples.
| Method | Purpose |
|---|---|
| Stub return value |
| Verify call happens |
ruby
undefined详细示例请参考references/patterns.md。
| 方法 | 用途 |
|---|---|
| 存根方法返回值 |
| 验证方法是否被调用 |
ruby
undefinedStubbing external service
存根外部服务
allow(PaymentGateway).to receive(:charge).and_return(true)
allow(PaymentGateway).to receive(:charge).and_return(true)
Verifying method called
验证方法调用
expect(UserMailer).to receive(:welcome_email).with(user)
undefinedexpect(UserMailer).to receive(:welcome_email).with(user)
undefinedMatchers Quick Reference
匹配器快速参考
See references/matchers.md for complete reference.
完整参考请见references/matchers.md。
Essential Matchers
核心匹配器
ruby
undefinedruby
undefinedEquality
相等性
expect(value).to eq(expected)
expect(value).to eq(expected)
Truthiness
真值判断
expect(obj).to be_valid
expect(obj).to be_truthy
expect(obj).to be_valid
expect(obj).to be_truthy
Change
状态变更
expect { action }.to change { obj.status }.to("completed")
expect { action }.to change(Model, :count).by(1)
expect { action }.to change { obj.status }.to("completed")
expect { action }.to change(Model, :count).by(1)
Errors
异常
expect { action }.to raise_error(SomeError)
expect { action }.to raise_error(SomeError)
Collections
集合
expect(array).to include(item)
expect(array).to be_empty
undefinedexpect(array).to include(item)
expect(array).to be_empty
undefinedShoulda Matchers
Shoulda Matchers
ruby
undefinedruby
undefinedValidations
校验规则
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_uniqueness_of(:email) }
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_uniqueness_of(:email) }
Associations
关联关系
it { is_expected.to have_many(:posts) }
it { is_expected.to belong_to(:account) }
undefinedit { is_expected.to have_many(:posts) }
it { is_expected.to belong_to(:account) }
undefinedAAA Pattern
AAA Pattern
Structure all tests as Arrange-Act-Assert:
ruby
describe "#process_refund" do
subject(:process_refund) { processor.process_refund }
let(:order) { orders(:completed_order) }
let(:processor) { described_class.new(order) }
it "updates order status" do
process_refund # Act
expect(order.reload.status).to eq("refunded") # Assert
end
it "credits user account" do
expect { process_refund } # Act
.to change { order.user.reload.account_balance } # Assert
.by(order.total)
end
end所有测试均需遵循Arrange-Act-Assert结构:
ruby
describe "#process_refund" do
subject(:process_refund) { processor.process_refund }
let(:order) { orders(:completed_order) }
let(:processor) { described_class.new(order) }
it "updates order status" do
process_refund # 执行操作
expect(order.reload.status).to eq("refunded") # 断言结果
end
it "credits user account" do
expect { process_refund } # 执行操作
.to change { order.user.reload.account_balance } # 断言结果
.by(order.total)
end
endTest Coverage Standards
测试覆盖标准
What to Test
测试范围
| Type | Test For |
|---|---|
| Models | Validations, associations, scopes, callbacks, methods |
| Services | Happy path, sad path, edge cases, external integrations |
| Controllers | Status codes, response formats, auth, redirects |
| Jobs | Execution, retry logic, error handling, idempotency |
| 类型 | 测试内容 |
|---|---|
| 模型 | 校验规则、关联关系、作用域、回调、方法 |
| 服务 | 正常流程、异常流程、边缘场景、外部集成 |
| 控制器 | 状态码、响应格式、权限验证、重定向 |
| 任务 | 执行逻辑、重试机制、错误处理、幂等性 |
Coverage Example
覆盖示例
ruby
RSpec.describe User do
fixtures :users
describe "validations" do
subject(:user) { users(:valid_user) }
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_presence_of(:email) }
it { is_expected.to validate_uniqueness_of(:email).case_insensitive }
end
describe "associations" do
it { is_expected.to have_many(:posts).dependent(:destroy) }
end
describe "#full_name" do
subject(:full_name) { user.full_name }
let(:user) { User.new(first_name: "Alice", last_name: "Smith") }
it { is_expected.to eq("Alice Smith") }
context "when last name is missing" do
let(:user) { User.new(first_name: "Alice") }
it { is_expected.to eq("Alice") }
end
end
endruby
RSpec.describe User do
fixtures :users
describe "validations" do
subject(:user) { users(:valid_user) }
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_presence_of(:email) }
it { is_expected.to validate_uniqueness_of(:email).case_insensitive }
end
describe "associations" do
it { is_expected.to have_many(:posts).dependent(:destroy) }
end
describe "#full_name" do
subject(:full_name) { user.full_name }
let(:user) { User.new(first_name: "Alice", last_name: "Smith") }
it { is_expected.to eq("Alice Smith") }
context "when last name is missing" do
let(:user) { User.new(first_name: "Alice") }
it { is_expected.to eq("Alice") }
end
end
endAnti-Patterns
反模式
See references/anti-patterns.md for detailed examples.
| Anti-Pattern | Why Bad |
|---|---|
| Redundant, loaded via .rspec |
| Redundant, inferred from location |
Leading | Violates RuboCop style |
| Empty test bodies | False confidence |
| Testing private methods | Couples to implementation |
| Not using fixtures | Slow tests |
| Not using shoulda | Verbose validation tests |
详细示例请参考references/anti-patterns.md。
| 反模式 | 危害 |
|---|---|
| 冗余操作,已通过.rspec加载 |
| 冗余配置,可通过文件位置自动推断 |
命名空间前加 | 违反RuboCop编码规范 |
| 空测试体 | 造成虚假的测试覆盖信心 |
| 测试私有方法 | 与实现细节耦合,易维护性差 |
| 不使用fixtures | 测试执行速度慢 |
| 不使用shoulda | 校验规则测试代码冗长 |
Best Practices Checklist
最佳实践检查清单
Critical Conventions:
- NOT adding
require 'rails_helper' - NOT adding redundant spec type
- Using namespace WITHOUT leading
::
Test Organization:
- for methods/classes
describe - for conditions
context - Max 3 levels nesting
Test Data:
- Using fixtures (not factories)
- Using for lazy data
let - Using for method under test
subject
Assertions:
- Shoulda matchers for validations
- Shoulda matchers for associations
- matcher for state changes
change
Coverage:
- Happy path tested
- Sad path tested
- Edge cases covered
关键约定:
- 不添加
require 'rails_helper' - 不添加冗余的spec类型
- 命名空间不使用前置
::
测试组织:
- 使用按方法/类分组
describe - 使用按条件场景分组
context - 嵌套层级不超过3层
测试数据:
- 使用fixtures(而非工厂类)
- 使用定义延迟加载数据
let - 使用定义待测试方法
subject
断言:
- 使用Shoulda Matchers测试校验规则
- 使用Shoulda Matchers测试关联关系
- 使用匹配器测试状态变更
change
覆盖范围:
- 已测试正常流程
- 已测试异常流程
- 已覆盖边缘场景
Quick Reference
快速参考
ruby
undefinedruby
undefinedMinimal spec file
最简spec文件
RSpec.describe User do
fixtures :users
describe "#full_name" do
subject(:full_name) { user.full_name }
let(:user) { users(:alice) }
it { is_expected.to eq("Alice Smith") }end
describe "validations" do
subject(:user) { users(:alice) }
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to have_many(:posts) }end
end
undefinedRSpec.describe User do
fixtures :users
describe "#full_name" do
subject(:full_name) { user.full_name }
let(:user) { users(:alice) }
it { is_expected.to eq("Alice Smith") }end
describe "validations" do
subject(:user) { users(:alice) }
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to have_many(:posts) }end
end
undefined