pytest多作用域fixture无冲突使用及parametrize简化方案问询
我希望隐藏多余的*@pytest.mark.parametrize*装饰器以保持测试代码整洁,现寻求其他可实现该需求的方案。
现有实现
当前可正常运行的代码如下:
@pytest.mark.parametrize('testcase', ['C34744: Switch to all size' 'C34742: Switch to all duration', 'C34743: Switch to all members']) @pytest.mark.parametrize('auth_f', [''], indirect=True, scope='function') @pytest.mark.parametrize('conference_f', ['testsuite_switch_filters'], indirect=True, scope='function') def testsuite(self, auth_f, conference_f, testcase): results = execute_actions('testsuite', testcase, auth_f) assert not results, "problems occured: " + results
目前该实现可正常运行,但不确定实现方式的正确性,希望隐藏多余的@pytest.mark.parametrize装饰器,了解其他可实现同等效果的机制。
使用的auth_f、conference_f两个fixture需要支持配置各类作用域,核心要求是二者运行时互不冲突,当前支持的作用域组合如下:
| first_fixt(scope) | second_fixt(scope) |
|---|---|
| function | function |
| module | function |
| function | module |
| module | module |
需要了解其他可实现上述效果的方式,另外当fixture之间存在依赖关系时(例如conference_f依赖auth_f),无法灵活配置上述多种作用域组合,会出现冲突问题。
期望实现效果
目标代码如下:
@pytest.mark.parametrize('testcase', ['C34744: Switch to all size' 'C34742: Switch to all duration', 'C34743: Switch to all members']) def testsuite(self, auth_f, conference_f, testcase): results = execute_actions('testsuite', testcase, auth_f) assert not results, "problems occured: " + results
即不需要额外添加以下两行装饰器配置:
@pytest.mark.parametrize('auth_f', [''], indirect=True, scope='function') @pytest.mark.parametrize('conference_f', ['testsuite_switch_filters'], indirect=True, scope='function')
以下三种方案均可以去掉重复的装饰器配置,同时支持灵活调整fixture作用域,解决依赖场景下的冲突问题:
方案1:直接在fixture层封装默认参数
不需要通过indirect parametrize传固定值,把默认配置逻辑直接写到fixture内部即可。示例:import pytest # 基础鉴权fixture,可独立配置scope @pytest.fixture(scope='function') def auth_f(request): # 原有auth_f初始化逻辑 return init_auth() # 会议配置fixture,显式声明对auth_f的依赖,可独立配置scope @pytest.fixture(scope='function') def conference_f(auth_f, request): # 默认加载testsuite_switch_filters配置,不需要外部传参 return init_conference(config='testsuite_switch_filters')这种写法下测试函数只需要保留testcase的parametrize装饰器,直接声明fixture入参即可使用。pytest原生支持不同作用域fixture的依赖处理:高作用域(如module)fixture依赖低作用域(如function)fixture时,会自动调整实例化时机适配作用域规则,完全覆盖列出的四种作用域组合,不会出现冲突。如果需要切换配置,只需要给fixture增加参数开关,或者新增对应配置的fixture别名即可。
方案2:自定义装饰器封装重复的parametrize逻辑
如果多个测试用例需要复用这组固定的fixture参数,可以把两行重复配置封装成单个自定义装饰器。示例:import pytest from functools import wraps def testsuite_default_fixtures(func): @wraps(func) @pytest.mark.parametrize('auth_f', [''], indirect=True, scope='function') @pytest.mark.parametrize('conference_f', ['testsuite_switch_filters'], indirect=True, scope='function') def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper使用时只需要在测试函数上加
@testsuite_default_fixtures即可,不需要重复写两行parametrize。如果需要切换作用域组合,只需要修改装饰器参数,或者新增对应作用域版本的装饰器。方案3:通过pytest钩子自动给用例追加标记
如果项目中大量测试用例都需要这组固定fixture配置,可以在conftest.py中编写用例收集钩子,自动给匹配规则的用例追加parametrize标记,测试代码不需要加任何额外装饰器。示例:# conftest.py def pytest_collection_modifyitems(config, items): for item in items: # 匹配testsuite相关用例,可根据自身项目规则调整匹配逻辑 if 'testsuite' in item.nodeid: item.add_marker(pytest.mark.parametrize('auth_f', [''], indirect=True, scope='function')) item.add_marker(pytest.mark.parametrize('conference_f', ['testsuite_switch_filters'], indirect=True, scope='function'))这种方式对测试代码零侵入,作用域、配置参数调整只需要统一维护钩子逻辑即可。
针对fixture依赖的作用域冲突问题,优先选择方案1,直接在fixture层声明依赖关系,比通过parametrize传参的方式更符合pytest的设计逻辑,不会出现作用域适配问题。
内容的提问来源于stack exchange,提问作者Firstname Lastname

