FactoryBot 完全指南:Ruby 测试数据工厂的正确用法

FactoryBot 是 Ruby 测试数据管理的事实标准。本文讲解工厂定义、属性序列、关联、trait 变体与 build/create 策略选择,帮你写出可读、可维护、不拖慢测试套的测试数据方案。

最佳实践
红宝石与应用开发工作台插画

FactoryBot 用"工厂"替代 Rails fixtures:每个用例现场构建所需数据,配合序列(sequence)保证唯一性、trait 表达状态变体、关联(association)组装对象图。默认用 build(不落库),只有需要数据库行为时才用 create,这是控制测试速度的关键。

为什么不用 fixtures

Rails 自带的 fixtures 是全局静态数据:所有用例共享同一份"万能数据",改一个字段可能炸掉二十个测试,用例和数据之间隔着文件,读测试时根本看不出它依赖什么。FactoryBot 的思路相反——每个测试按需构建自己需要的数据,依赖关系写在用例里,一眼可见。

安装与第一个工厂

Gemfile 测试组加入 gem 'factory_bot_rails',RSpec 配置 config.include FactoryBot::Syntax::Methods 后即可定义工厂:

FactoryBot.define do
  factory :user do
    name  { 'Alice' }
    email { 'alice@example.com' }
    admin { false }
  end
end

用例中使用:

user = build(:user)          # 内存对象,不落库
user = create(:user)         # 写入数据库
attrs = attributes_for(:user) # 只拿属性 Hash
create(:user, name: 'Bob')   # 现场覆盖属性

四种构建策略的选择

策略 行为 适用场景
build 只实例化 纯对象逻辑测试,速度最快
create 实例化+落库 需要回调、校验、唯一索引、关联查询
build_stubbed 伪造已持久化 需要 id 和"已保存"假象,不碰数据库
attributes_for 返回 Hash 控制器参数测试

测试套变慢的元凶往往是无脑 create——先问自己:这个断言真的需要数据库吗?

sequence:唯一性的保障

邮箱、用户名这类带唯一约束的字段,固定值会在第二次 create 时报冲突:

factory :user do
  sequence(:email) { |n| "user#{n}@example.com" }
  sequence(:name)  { |n| "用户#{n}" }
end

每次调用自增,永远唯一。

association 与 trait:表达真实数据形态

factory :post do
  association :author, factory: :user
  title { '默认标题' }

  trait :published do
    status { 'published' }
    published_at { 1.day.ago }
  end

  trait :with_comments do
    after(:create) { |post| create_list(:comment, 3, post: post) }
  end
end

create(:post, :published, :with_comments)

association 自动构建关联对象;trait 把"已发布""带评论"这类状态变体命名化,用例意图一目了然;after(:create) 回调处理需要双向关联的复杂场景。

保持测试套健康的纪律

  • 工厂保持最小化:默认工厂只填必填字段,特殊状态一律走 trait——肥工厂是隐性耦合的温床;
  • 警惕隐式关联雪崩:post 带 author、author 带 team、team 带 organization……一次 create 建十张表,用 build_stubbed 或裁剪关联打断链条;
  • 配合 let 惰性构建:RSpec 的 let 只在引用时触发,避免 before 块里建一堆用不上的数据。

常见问题(FAQ)

Q:FactoryBot 和 fixtures 能混用吗?

可以但别在同一张表上混。常见做法是:枚举型静态数据(国家、角色)用 fixtures,业务对象用 FactoryBot。

Q:测试里 create 报 validation 失败怎么排查?

先在 rails console 里手动跑同样的 FactoryBot.create(:xxx) 复现;十有八九是模型新增校验后工厂没同步更新。养成"加校验时同步检查工厂"的习惯。

Q:create_list 一次建几百条很慢怎么办?

能断言数量就别建满;必须批量时用 create_list(:x, 100) 而非循环 create(内部有批量优化),或改用 insert_all 直接插数据跳过回调。


参考文档

本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。

延伸阅读

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台