如何在pytest中跳过测试但仍按原顺序执行session fixtures?
问题背景与需求
我维护一个大型Selenium测试套件,用session级fixture在被测Web应用中创建测试数据(比如地点、人员、状态这类用户聚合数据)。所有依赖这类数据的测试都会关联对应fixture,每个fixture负责在应用中操作生成数据(套件默认每次运行前数据库已清空)。
这种设计能让我们运行任意测试子集,不会有数据依赖问题。简单测试模块示例:
test_a(fixture_a) test_b(fixture_b) test_c(fixture_a)
测试套件按指定模块顺序执行:
module_1 module_2 module_3
套件支持通过标签从指定模块开始顺序运行(比如从module_2开始,跳过module_1,后续正常执行)。
核心问题:
跳过测试时,fixture的执行顺序会被打乱,甚至可能不执行。比如跳过test_a后,fixture_a要等到test_c执行时才会运行。我需要实现:跳过指定测试,但仍让对应的fixture按原有顺序执行。
可行解决方案
1. 显式指定fixture执行顺序
利用pytest-order插件(需提前安装:pip install pytest-order)给fixture标记执行顺序,强制fixture按设定顺序运行,不受测试调用时机影响。
示例代码:
import pytest @pytest.mark.order(1) @pytest.fixture(scope="session") def fixture_a(): # 生成地点数据的逻辑 return "地点数据" @pytest.mark.order(2) @pytest.fixture(scope="session") def fixture_b(): # 生成人员数据的逻辑 return "人员数据"
如果fixture之间有逻辑依赖,也可以通过fixture嵌套调用固定顺序,比如让fixture_b依赖fixture_a,确保fixture_a先执行。
2. 添加预执行空测试
在模块中添加仅用于触发fixture的空测试,给这类测试单独标记。当需要跳过业务测试但保留fixture执行时,只跳过业务测试,保留预执行测试。
示例代码:
import pytest # 预执行测试:仅触发对应fixture,无业务逻辑 @pytest.mark.pre_run def test_trigger_fixture_a(fixture_a): pass @pytest.mark.pre_run def test_trigger_fixture_b(fixture_b): pass # 业务测试 @pytest.mark.business def test_a(fixture_a): # 业务测试逻辑 pass @pytest.mark.business def test_b(fixture_b): # 业务测试逻辑 pass @pytest.mark.business def test_c(fixture_a): # 业务测试逻辑 pass
运行时跳过test_a但保留fixture执行的命令:
pytest -m "pre_run or (business and not test_a)"
3. 自定义pytest钩子自动触发fixture
通过pytest_collection_modifyitems钩子,在测试收集阶段自动插入虚拟测试来触发指定fixture,确保fixture按顺序执行。
示例钩子实现(放在conftest.py中):
def pytest_collection_modifyitems(config, items): # 定义需要提前执行的fixture列表,按顺序排列 required_fixtures = ["fixture_a", "fixture_b"] from _pytest.python import Function import types for fixture_name in required_fixtures: # 创建虚拟测试函数,仅获取fixture def dummy_test(request): request.getfixturevalue(fixture_name) # 包装成pytest可识别的测试项 dummy_func = types.FunctionType(dummy_test.__code__, globals(), f"test_trigger_{fixture_name}") dummy_item = Function.from_parent(config.rootpath, func=dummy_func) # 将虚拟测试插入到测试列表的最前面 items.insert(0, dummy_item)
这样无论哪些测试被跳过,指定的fixture都会在测试开始前按顺序执行。
4. 抽离数据初始化脚本
把fixture中的数据生成逻辑单独抽成初始化脚本,运行测试前手动或通过脚本调用需要的初始化步骤,再执行测试。
示例初始化脚本(data_init.py):
def init_fixture_a(): # 生成地点数据的Selenium操作逻辑 print("初始化fixture_a数据") def init_fixture_b(): # 生成人员数据的Selenium操作逻辑 print("初始化fixture_b数据") if __name__ == "__main__": import sys if len(sys.argv) < 2: print("请指定要初始化的fixture,如:python data_init.py fixture_a") sys.exit(1) fixture_name = sys.argv[1] if fixture_name == "fixture_a": init_fixture_a() elif fixture_name == "fixture_b": init_fixture_b()
运行流程:
# 先初始化fixture_a数据 python data_init.py fixture_a # 跳过test_a运行测试 pytest -k "not test_a"
内容的提问来源于stack exchange,提问作者Marcel Wilson

