pytest-xdist触发数据库唯一键冲突是因为多进程并发执行时多个worker同时向同一数据库表插入相同唯一值(如username="admin"),而默认配置未实现进程间数据隔离;根本解法是让每个worker操作独立数据空间,推荐动态分配独立数据库名、标记串行执行或用factory_boy动态生成唯一值,并确保所有fixture和初始化逻辑均适配。

为什么 pytest-xdist 会触发数据库唯一键冲突?
pytest-xdist 默认开启多进程并发执行测试,多个进程同时运行 test_create_user 这类创建固定用户名(如 "admin")的测试时,会几乎同时向数据库插入相同 username,违反唯一约束。这不是代码逻辑错误,而是测试隔离失效——每个进程共享同一套数据库连接和初始数据状态。
用 pytest-django 的 db marker 配合 --reuse-db 不够用
@pytest.mark.django_db 确保测试有数据库访问权限,但默认不隔离事务或数据库实例;--reuse-db 只跳过 migrate,所有进程仍连同一个库。冲突依然发生。
- 根本问题不在是否建库,而在「多个进程写同一张表的同一字段」
-
--reuse-db+--nomigrations会加速冲突,因为没有清库环节 - Django 的
TransactionTestCase在 xdist 下也不自动隔离进程间数据
三种可靠解法及适用场景
核心思路:让每个 worker 操作互不干扰的数据空间。
-
方案一(推荐):为每个 xdist worker 分配独立数据库名
在conftest.py中动态生成库名,例如f"testdb_{os.environ.get('PYTEST_XDIST_WORKER', 'gw0')}",再通过DATABASES['default']['NAME']覆盖。需确保 PostgreSQL/MySQL 支持快速建库,且测试前用脚本预建空库(或用django-db-geventpool类工具配合) -
方案二:强制单进程跑相关测试集
对涉及固定唯一值的测试文件加标记:@pytest.mark.serial,再运行pytest -m "not serial" -n auto && pytest -m serial -n 0。适合少量关键测试,避免大改 -
方案三:用 factory_boy 动态生成唯一值
把username="admin"改成Faker().user_name()或Sequence(lambda n: f"user_{n}")。注意:必须确保整个测试函数内只读一次该字段(否则多次调用可能生成不同值,断言失败)
最容易被忽略的坑:fixture 里的硬编码值没改
很多人只改了测试函数里的 "admin",却忘了 conftest.py 里定义的 @pytest.fixture 如 admin_user 仍返回固定用户名。只要这个 fixture 被多个测试复用,冲突照旧。检查所有 fixture 返回值、model 实例初始化参数、以及 setUpTestData 中的创建逻辑——它们都得走动态生成或 worker-aware 分离路径。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











