直接用 Model.objects.create() 写测试数据易导致速度慢、级联错误和验证失败;应优先用 factory_boy 处理外键依赖,谨慎使用 patch,推荐 override_settings 禁用数据库。

为什么直接用 Model.objects.create() 写测试数据容易出问题
因为每次跑测试都真实写入数据库,哪怕用 TestCase 自带的事务回滚,也会拖慢速度;更麻烦的是,当模型有 ForeignKey、ManyToManyField 或自定义 save() 逻辑时,硬创建容易触发级联、信号或验证失败,导致测试不稳定。
实操建议:
- 优先用
factory_boy(配合DjangoModelFactory)替代手写create(),它能延迟生成、自动处理外键依赖、支持策略化构造 - 避免在
setUp()里批量create()多个实例——改用build_batch()+bulk_create()(仅限无信号/自定义 save 的场景) - 如果只是校验序列化器或视图逻辑,考虑用
model_to_dict()+type('MockModel', (), {...})构造轻量模拟对象,绕过 ORM
如何让 factory_boy 正确处理 Django 的 ForeignKey 和 OneToOneField
默认行为是每调用一次 factory.SubFactory 就新建一个关联对象,容易造成冗余数据和外键冲突。比如用户工厂里嵌套地址工厂,10 个用户会建 10 个独立地址,但测试可能只需要 1 个共享地址。
实操建议:
- 对需复用的关联对象,提前在
setUpTestData()中创建一次,再通过factory.LazyAttribute引用:address = factory.LazyAttribute(lambda _: cls.shared_address) - 用
factory.Iterator轮询预设值,避免主键冲突:status = factory.Iterator(['draft', 'published']) - 慎用
factory.PostGeneration处理多对多——它在实例保存后才执行,若中间表有非空约束,得先save()再add(),否则报ValueError: "Model" needs to have a value for field "id" before this many-to-many relationship can be used.
测试中 Mock 数据库查询时,patch 和 override_settings 哪个更安全
patch('myapp.views.MyModel.objects.filter') 看似简单,但极易漏掉链式调用(如 .filter().exclude().order_by()),且无法覆盖 select_related、prefetch_related 这类 ORM 优化逻辑。
实操建议:
- 优先用
override_settings(DATABASES={'default': {'ENGINE': 'django.db.backends.dummy'}})彻底禁用 DB,再配合unittest.mock.Mock返回预设 QuerySet(如Mock(return_value=[obj1, obj2])) - 若必须 patch,目标要精确到最终执行方法:
patch('django.db.models.sql.compiler.SQLCompiler.execute_sql'),而非高层 API - 永远检查是否 patch 了正确的位置——在被测模块内导入的路径,不是定义路径(常见错误:patch
'myapp.models.MyModel.objects'而实际代码里是from django.db import models后调用models.Manager)
用 pytest-django 时,transactional_db fixture 的代价和替代方案
启用 transactional_db 意味着每个测试函数都开新事务,虽保证隔离,但启动耗时翻倍,尤其在 CI 上叠加几百个测试时明显卡顿。
实操建议:
- 仅对真正需要跨事务验证的测试(如 Celery 任务、信号触发、数据库锁)显式加
@pytest.mark.django_db(transaction=True) - 其余测试保持默认的
django_db(基于 TestCase 的事务回滚),并确保不手动调用transaction.commit() - 高频读操作测试可改用内存 SQLite:
override_settings(DATABASES={'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:'}}),但注意它不支持某些 PostgreSQL 特性(如 JSONField 的高级查询)
最常被忽略的一点:工厂类里的 class Meta: 必须明确指定 model = MyModel,漏写会导致 factory.build() 返回 dict 而非 model 实例,后续调用 .save() 直接抛 AttributeError。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











