如何在多个测试用例间共享函数作用域fixture初始化的数据
Pytest中为只读测试共享高开销数据库Fixture的解决方案
问题背景
你有一个开销极大的数据库初始化Fixture(创建表、插入数据),因常规测试会修改数据状态,所以设为函数作用域。但存在一批只读查询测试,可共享同一数据库状态以提升效率,现有方案均存在缺陷:
pytest.mark.parametrize会重复执行Fixture,效率无提升;- 改为模块作用域会与pytest-asyncio的函数作用域事件循环冲突,且无法仅针对这批测试切换作用域;
- 单测试函数内循环执行无法定位失败用例,也不便单独运行。
解决方案一:为只读测试创建专属模块作用域Fixture
针对只读测试单独维护一套模块作用域的Fixture,既保证状态共享,又不影响常规测试的函数作用域Fixture。
实现步骤
- 将所有只读测试集中到一个独立模块(如
test_read_only_queries.py); - 在该模块内定义模块作用域的数据库Fixture,配合pytest-asyncio的异步Fixture支持:
import pytest import pytest_asyncio from your_db_module import init_database, get_db_connection @pytest_asyncio.fixture(scope="module") async def module_scoped_db(): # 仅初始化一次数据库 await init_database() db_conn = await get_db_connection() yield db_conn # 只读测试若无需清理可省略此步骤 await db_conn.close() # 只读测试使用该模块作用域Fixture @pytest.mark.asyncio async def test_user_list_query(module_scoped_db): result = await module_scoped_db.fetch("SELECT * FROM users") assert len(result) > 0 @pytest.mark.asyncio async def test_product_count_query(module_scoped_db): result = await module_scoped_db.fetchval("SELECT COUNT(*) FROM products") assert result == 100
注意事项
- 虽然pytest-asyncio官方不建议将事件循环改为模块作用域,但只读测试无状态修改,不会引发并发安全问题,风险可控;
- 该Fixture仅作用于当前模块,不会影响其他测试文件的函数作用域Fixture。
解决方案二:使用pytest-subtests将循环用例转为独立子测试
保持原函数作用域Fixture不变,通过pytest-subtests插件将循环内的测试逻辑转为pytest可识别的独立子测试,既共享Fixture实例,又能单独定位失败用例。
实现步骤
- 安装插件:
pip install pytest-subtests
- 编写测试函数,用
subtests.test()包裹每个只读查询的测试逻辑:
import pytest from your_db_module import init_database, get_db_connection, read_query_list @pytest.fixture(scope="function") async def db(): await init_database() db_conn = await get_db_connection() yield db_conn await db_conn.close() @pytest.mark.asyncio async def test_all_read_queries(db, subtests): # read_query_list为包含查询名称和执行逻辑的字典/列表 for query_name, query_func in read_query_list.items(): with subtests.test(msg=query_name): result = await query_func(db) # 针对该查询的断言逻辑 assert result is not None assert len(result) > 0
效果说明
- 运行测试时,每个子测试会被pytest当作独立条目展示,失败时会明确标记对应的查询名称;
- 可通过
pytest -k "查询名称"单独运行指定的子测试; - Fixture仅执行一次(因为所有子测试在同一个测试函数内),达到状态共享的目的。
内容的提问来源于stack exchange,提问作者Adam Budziak
相关产品推荐
相关产品推荐

