Django Pytest中parametrize装饰器如何实现数据库访问
问题原因
@pytest.mark.parametrize的参数列表在测试收集阶段就会被解析执行,这个阶段pytest-django还没完成Django测试环境和数据库连接的初始化,默认拦截所有数据库访问请求,所以会抛出数据库访问不被允许的错误。测试方法内部可以正常访问数据库,是因为执行到测试逻辑时,django_db标记已经触发了数据库初始化流程。
运行时抛出的具体错误为:
RuntimeError: Database access not allowed, use the "django_db" mark, or the "db" or "transactional_db" fixtures to enable it.
可选解决方案
方案1:测试内部遍历查询(实现最简单)
如果不需要每个Foo表条目对应一条独立的测试报告项,直接在测试方法内部查询数据遍历执行断言即可,完全适配pytest-django的数据库生命周期,不会出现权限问题:
import pytest import os from django.conf import settings from app.models import Foo pytestmark = pytest.mark.django_db @pytest.fixture(scope='session') def django_db_setup(): settings.DATABASES['default'] = { 'ENGINE': 'django.db.backends.mysql', 'NAME': os.environ['DB_NAME'], 'USER': os.environ['DB_USER'], 'PASSWORD': os.environ['DB_PASSWORD'], 'HOST': 'localhost', } @pytest.mark.django_db class TestFoo: def test_foo_objects(self): for foo in Foo.objects.all(): # 断言加对象标识,方便定位失败条目 assert foo.is_ok, f"ID为{foo.id}的Foo条目状态异常"
方案2:通过pytest_generate_tests钩子动态参数化(和原生parametrize效果完全一致)
如果需要每个Foo条目对应一条独立测试用例(和直接写@pytest.mark.parametrize的展示效果、执行逻辑完全一致,单条失败不影响其他条目执行),可以使用pytest的pytest_generate_tests钩子动态生成参数,这个钩子执行时可以触发数据库初始化,再查询数据完成参数化:
在测试文件或者conftest.py中添加如下钩子逻辑:
def pytest_generate_tests(metafunc): # 仅对入参包含foo的测试方法做参数化 if 'foo' in metafunc.fixturenames: # 注入db依赖,触发pytest-django初始化数据库连接 metafunc.fixturenames.append('db') from app.models import Foo # 查询所有Foo条目作为参数 foo_list = list(Foo.objects.all()) metafunc.parametrize('foo', foo_list)
对应的测试类可以简化为:
@pytest.mark.django_db class TestFoo: def test_foo_objects(self, foo): assert foo.is_ok
注意事项
- 不要尝试在
@pytest.mark.parametrize的参数位置直接编写数据库查询逻辑,该阶段必然没有数据库访问权限 - 如果测试过程中不会修改Foo表数据,可以在查询时加缓存逻辑,减少重复查询的性能开销
- 两种方案都默认走pytest-django的测试数据库隔离逻辑,不会污染原有数据库数据
内容的提问来源于stack exchange,提问作者Egor Wexler
相关产品推荐
相关产品推荐

