Factory Boy 是用于简化 Django 测试中模型实例创建的工具,通过声明式默认值、按需覆盖和自动处理外键关系,解决手动调用 model.objects.create() 时依赖繁琐、维护困难的问题。

Factory Boy 是什么,为什么不用 Django 自带的 model.objects.create()
它不是“替代 create()”,而是解决 create() 在测试中写起来累、改起来疼的问题。比如要测一个带 5 个外键、3 个非空字段、2 个时间自动填充的 Order 模型,手写 Order.objects.create(...) 每次都要补全依赖对象,一改模型字段就得同步修所有测试用例。
Factory Boy 的核心价值是:声明式定义默认值 + 按需覆盖 + 自动处理外键/OneToOne 关系。它不运行数据库迁移,也不要求你提前建好表,只要 Django settings 配好了就能用。
- 必须在
settings.py中配置好DATABASES,哪怕只用 SQLite 内存库(sqlite3:///:memory:) - 工厂类必须继承
factory.django.DjangoModelFactory,不能用普通factory.Factory - 字段名必须和模型字段严格一致,包括
_set反向关系名(如user_set),否则会静默忽略
怎么写一个基础工厂类(以 User 和 Profile 为例)
假设你有 User(Django 自带)和自定义的 Profile(OneToOneField(User))。常见错误是直接在 ProfileFactory 里写 user = factory.SubFactory(UserFactory) 却没定义 UserFactory,结果报 NameError: name 'UserFactory' is not defined。
正确做法是先定义 UserFactory,再在 ProfileFactory 中引用;同时注意 username 必须唯一,得用 factory.Sequence 自动生成:
import factory
from django.contrib.auth.models import User
from myapp.models import Profile
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = User
username = factory.Sequence(lambda n: f"user_{n}")
email = factory.LazyAttribute(lambda obj: f"{obj.username}@example.com")
password = factory.PostGenerationMethodCall("set_password", "password123")
class ProfileFactory(factory.django.DjangoModelFactory):
class Meta:
model = Profile
user = factory.SubFactory(UserFactory)
bio = "test bio"
-
factory.PostGenerationMethodCall("set_password", ...)是必须的,否则User密码未哈希,登录测试会失败 - 不要在工厂里调用
save()或full_clean()——DjangoModelFactory已自动处理 - 如果模型有
auto_now_add=True字段(如created_at),Factory Boy 默认不填,Django 会自动设值;若需控制该时间,显式赋值即可
测试中怎么用,以及容易卡住的三个点
工厂类本身不创建数据,调用 UserFactory() 才真正写入数据库。最常踩的坑不是语法错,而是事务/数据库状态没理清:
- 在
TestCase中用UserFactory()没问题;但在TransactionTestCase或使用pytest-django的dbfixture 时,要注意是否开了事务回滚 —— Factory Boy 创建的对象也会被回滚 - 外键字段名写错(比如把
author写成user),不会报错,但生成的对象关联为空,查数据时返回None,debug 花半小时才发现字段名对不上 - 用
UserFactory.create_batch(5)生成 5 个用户时,username会按序列递增(user_0到user_4);但如果批量里某个实例想单独覆盖字段,得用UserFactory.create(username="special"),不能对 batch 结果再赋值
和 Faker 混用时的关键细节
Factory Boy 本身不生成假数据,靠集成 Faker(pip install faker)来填内容。但 Faker 实例不是全局共享的,每次调用 factory.Faker("name") 都新建一个,所以中文支持要显式指定 locale:
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = User
username = factory.Faker("user_name", locale="zh_CN")
first_name = factory.Faker("first_name", locale="zh_CN")
last_name = factory.Faker("last_name", locale="zh_CN")
- 不写
locale="zh_CN",默认是英文,生成的first_name是 "John",不是你想要的“张” -
factory.Faker("email")生成的邮箱域名可能重复(如全是@example.com),若测试依赖邮箱唯一性,得自己拼接或用factory.Sequence - Faker 的 provider(如
address)在不同 locale 下字段含义可能不同,factory.Faker("address", locale="zh_CN")返回的是中文地址字符串,但字段长度可能超模型限制,记得检查max_length
复杂点在于嵌套关系深度和数据库约束的耦合 —— 比如一个 Order 工厂要拉起 User、Address、Product、Category 四层,其中任意一层字段变更都可能让整条链失效。这时候别硬堆工厂,该拆就拆,用 params 控制可选依赖更稳。










