关于 rspec-coder
面向编写 RSpec 测试的开发者,提供 Rails 项目规范及组织规格、使用 fixtures、匹配器和模拟对象的示例。强调关注行为、相互独立,并覆盖正常情况与边缘情况。
主要功能
- 01AAA 结构与行为导向测试
- 02RSpec 文件组织和命名规范
- 03fixtures、subject 与 let 用法
- 04模拟、存根及匹配器示例
- 05常见 Rails 组件的覆盖指导
适用场景
- 01按清晰的上下文编写模型和服务规格
- 02用存根和调用期望测试外部调用
- 03使用 Shoulda Matchers 检查验证与关联
- 04检查规格是否覆盖错误及边缘情况
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ majesticlabs-dev/ majestic-marketplace --skill rspec-coder安装后在 Claude Code 中输入/rspec-coder确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/anti-patterns.mdmatchers.mdpatterns.md详细介绍
RSpec 编码器
核心理念
- AAA 模式:采用 Arrange-Act-Assert(准备-执行-断言)结构,清晰明了
- 关注行为,而非实现:测试代码的行为,而不是实现方式
- 隔离性:测试应相互独立
- 描述性命名:代码块应清楚说明其行为
- 覆盖率:测试正常路径和边界情况
- 快速测试:尽量减少数据库操作
- Fixtures:使用 fixtures 设置常用数据
- Shoulda Matchers:用于验证和关联
关键约定
❌ 不要添加 require 'rails_helper'
RSpec 会通过 .rspec 配置导入。手动添加是多余的。
# ✅ GOOD - no require needed
RSpec.describe User do
# ...
end
❌ 不要添加冗余的 Spec 类型
RSpec 会根据文件位置自动推断类型。
# ✅ GOOD - type inferred from spec/models/ location
RSpec.describe User do
# ...
end
✅ 使用不带前导 :: 的命名空间
# ✅ GOOD - no leading double colons
RSpec.describe DynamicsGp::ERPSynchronizer do
# ...
end
测试组织
文件结构
spec/models/- 模型单元测试spec/services/- 服务对象测试spec/controllers/- 控制器测试spec/requests/- 请求规格(API 测试)spec/mailers/- 邮件发送器测试spec/jobs/- 后台作业测试spec/fixtures/- 测试数据spec/support/- 辅助模块和共享示例spec/rails_helper.rb- Rails 专用配置
使用 describe 和 context
| 代码块 | 用途 | 示例 |
|---|---|---|
describe | 按方法/类分组 | describe "#process" |
context | 按条件分组 | context "when user is admin" |
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
Subject 和 Let
详细示例请参见 references/patterns.md。
| 模式 | 用例 |
|---|---|
subject(:name) { ... } | 要测试的主要对象/方法 |
let(:name) { ... } | 延迟求值并缓存的数据 |
let!(:name) { ... } | 立即求值(在每项测试之前) |
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
Fixtures
详细示例请参见 references/patterns.md。
# spec/fixtures/users.yml
alice:
name: Alice Smith
email: [email protected]
admin: false
RSpec.describe User do
fixtures :users
it "validates email" do
expect(users(:alice)).to be_valid
end
end
模拟和桩替换
详细示例请参见 references/patterns.md。
| 方法 | 用途 |
|---|---|
allow(obj).to receive(:method) | 桩替换返回值 |
expect(obj).to receive(:method) | 验证调用是否发生 |
# Stubbing external service
allow(PaymentGateway).to receive(:charge).and_return(true)
# Verifying method called
expect(UserMailer).to receive(:welcome_email).with(user)
Matcher 快速参考
完整参考请参见 references/matchers.md。
必备 Matchers
# Equality
expect(value).to eq(expected)
# Truthiness
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)
# Errors
expect { action }.to raise_error(SomeError)
# Collections
expect(array).to include(item)
expect(array).to be_empty
Shoulda Matchers
# Validations
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) }
AAA 模式
将所有测试组织为 Arrange-Act-Assert(准备-执行-断言):
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
测试覆盖率标准
测试内容
| 类型 | 测试内容 |
|---|---|
| 模型 | 验证、关联、作用域、回调、方法 |
| 服务 | 正常路径、异常路径、边界情况、外部集成 |
| 控制器 | 状态码、响应格式、身份验证、重定向 |
| 作业 | 执行、重试逻辑、错误处理、幂等性 |
覆盖率示例
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
end
反模式
详细示例请参见 references/anti-patterns.md。
| 反模式 | 不良之处 |
|---|---|
require 'rails_helper' | 多余,通过 .rspec 加载 |
type: :model | 多余,根据文件位置推断 |
命名空间前导 :: | 违反 RuboCop 风格 |
| 空测试主体 | 造成虚假的信心 |
| 测试私有方法 | 与实现耦合 |
| 不使用 fixtures | 测试速度慢 |
| 不使用 shoulda | 验证测试冗长 |
最佳实践检查清单
关键约定:
- 不添加
require 'rails_helper' - 不添加冗余的 spec 类型
- 使用不带前导
::的命名空间
测试组织:
- 使用
describe描述方法/类 - 使用
context描述条件 - 嵌套层级最多 3 层
测试数据:
- 使用 fixtures(而非 factories)
- 使用
let定义延迟加载的数据 - 使用
subject指定要测试的方法
断言:
- 使用 Shoulda matchers 验证
- 使用 Shoulda matchers 检查关联
- 使用
changematcher 检查状态变化
覆盖率:
- 已测试正常路径
- 已测试异常路径
- 已覆盖边界情况
快速参考
# Minimal spec file
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
评论
支持 Markdown