rspec-coder

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

RSpec 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'

RSpec imports via
.rspec
config. Adding manually is redundant.
ruby
undefined
RSpec会通过
.rspec
配置文件自动导入,手动添加属于冗余操作。
ruby
undefined

✅ GOOD - no require needed

✅ 正确写法 - 无需添加require

RSpec.describe User do

...

end
undefined
RSpec.describe User do

...

end
undefined

❌ Don't Add Redundant Spec Type

❌ 不要添加冗余的Spec类型

RSpec infers type from file location automatically.
ruby
undefined
RSpec会根据文件位置自动推断类型。
ruby
undefined

✅ GOOD - type inferred from spec/models/ location

✅ 正确写法 - 类型由spec/models/目录自动推断

RSpec.describe User do

...

end
undefined
RSpec.describe User do

...

end
undefined

✅ Use Namespace WITHOUT Leading
::

✅ 使用命名空间时不要加前置
::

ruby
undefined
ruby
undefined

✅ GOOD - no leading double colons

✅ 正确写法 - 不要加前置双冒号

RSpec.describe DynamicsGp::ERPSynchronizer do

...

end
undefined
RSpec.describe DynamicsGp::ERPSynchronizer do

...

end
undefined

Test Organization

测试组织

File Structure

文件结构

  • spec/models/
    - Model unit tests
  • spec/services/
    - Service object tests
  • spec/controllers/
    - Controller tests
  • spec/requests/
    - Request specs (API testing)
  • spec/mailers/
    - Mailer tests
  • spec/jobs/
    - Background job tests
  • spec/fixtures/
    - Test data
  • spec/support/
    - Helper modules and shared examples
  • spec/rails_helper.rb
    - Rails-specific configuration
  • spec/models/
    - 模型单元测试
  • spec/services/
    - 服务对象测试
  • spec/controllers/
    - 控制器测试
  • spec/requests/
    - 请求测试(API测试)
  • spec/mailers/
    - 邮件器测试
  • spec/jobs/
    - 后台任务测试
  • spec/fixtures/
    - 测试数据
  • spec/support/
    - 辅助模块和共享示例
  • spec/rails_helper.rb
    - Rails专属配置文件

Using
describe
and
context

使用
describe
context

BlockPurposeExample
describe
Groups by method/class
describe "#process"
context
Groups by condition
context "when user is admin"
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
代码块用途示例
describe
按方法/类分组
describe "#process"
context
按条件场景分组
context "when user is admin"
ruby
RSpec.describe OrderProcessor do
  describe "#process" do
    context "with valid payment" do
      # 成功场景测试
    end

    context "with invalid payment" do
      # 失败场景测试
    end
  end
end

Subject and Let

Subject与Let

See references/patterns.md for detailed examples.
PatternUse Case
subject(:name) { ... }
Primary object/method under test
let(:name) { ... }
Lazy-evaluated, memoized data
let!(:name) { ... }
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
模式使用场景
subject(:name) { ... }
测试的核心对象/方法
let(:name) { ... }
延迟求值、可缓存的数据
let!(:name) { ... }
立即求值(在每个测试前执行)
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

Fixtures

Fixtures

See references/patterns.md for detailed examples.
yaml
undefined
详细示例请参考references/patterns.md
yaml
undefined

spec/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
end
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
end

Mocking and Stubbing

Mocking与Stubbing

See references/patterns.md for detailed examples.
MethodPurpose
allow(obj).to receive(:method)
Stub return value
expect(obj).to receive(:method)
Verify call happens
ruby
undefined
详细示例请参考references/patterns.md
方法用途
allow(obj).to receive(:method)
存根方法返回值
expect(obj).to receive(:method)
验证方法是否被调用
ruby
undefined

Stubbing 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)
undefined
expect(UserMailer).to receive(:welcome_email).with(user)
undefined

Matchers Quick Reference

匹配器快速参考

See references/matchers.md for complete reference.
完整参考请见references/matchers.md

Essential Matchers

核心匹配器

ruby
undefined
ruby
undefined

Equality

相等性

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
undefined
expect(array).to include(item) expect(array).to be_empty
undefined

Shoulda Matchers

Shoulda Matchers

ruby
undefined
ruby
undefined

Validations

校验规则

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) }
undefined
it { is_expected.to have_many(:posts) } it { is_expected.to belong_to(:account) }
undefined

AAA 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
end

Test Coverage Standards

测试覆盖标准

What to Test

测试范围

TypeTest For
ModelsValidations, associations, scopes, callbacks, methods
ServicesHappy path, sad path, edge cases, external integrations
ControllersStatus codes, response formats, auth, redirects
JobsExecution, 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
end
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
end

Anti-Patterns

反模式

See references/anti-patterns.md for detailed examples.
Anti-PatternWhy Bad
require 'rails_helper'
Redundant, loaded via .rspec
type: :model
Redundant, inferred from location
Leading
::
in namespace
Violates RuboCop style
Empty test bodiesFalse confidence
Testing private methodsCouples to implementation
Not using fixturesSlow tests
Not using shouldaVerbose validation tests
详细示例请参考references/anti-patterns.md
反模式危害
require 'rails_helper'
冗余操作,已通过.rspec加载
type: :model
冗余配置,可通过文件位置自动推断
命名空间前加
::
违反RuboCop编码规范
空测试体造成虚假的测试覆盖信心
测试私有方法与实现细节耦合,易维护性差
不使用fixtures测试执行速度慢
不使用shoulda校验规则测试代码冗长

Best Practices Checklist

最佳实践检查清单

Critical Conventions:
  • NOT adding
    require 'rails_helper'
  • NOT adding redundant spec type
  • Using namespace WITHOUT leading
    ::
Test Organization:
  • describe
    for methods/classes
  • context
    for conditions
  • Max 3 levels nesting
Test Data:
  • Using fixtures (not factories)
  • Using
    let
    for lazy data
  • Using
    subject
    for method under test
Assertions:
  • Shoulda matchers for validations
  • Shoulda matchers for associations
  • change
    matcher for state changes
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
undefined
ruby
undefined

Minimal 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
undefined
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
undefined