如何为Pytest实现Fixture Maker装饰器以简化重复代码?
如何实现Pytest的
maker装饰器简化工厂Fixture模式 问题描述
我频繁使用以下Pytest Fixture模式:
@pytest.fixture() def make_stuff(fix1, fix2): def _fn(arg1, arg2): ... do something ... return _fn ... def test_me(make_stuff): make_stuff(1, 2)
为遵循DRY原则,我希望创建一个maker装饰器来简化该模式,预期写法如下:
@pytest.fixture() @pytest.maker(fix1, fix2) def make_stuff(arg1, arg2): ... do something ...
但不清楚如何将该想法集成到Pytest框架中,求可行思路。
实现方案
1. 核心装饰器实现
要实现这个maker装饰器,核心是将目标函数包装为一个返回内部函数的Fixture结构,同时处理Pytest的依赖注入逻辑。代码如下:
import pytest def maker(*deps): def decorator(func): def fixture_wrapper(*fixture_args, **fixture_kwargs): # 接收Pytest注入的依赖参数 def inner(*func_args, **func_kwargs): # 合并依赖参数与测试传入参数,调用原函数 return func(*fixture_args, *func_args, **fixture_kwargs, **func_kwargs) return inner # 保留原函数元数据,确保Pytest能正确识别Fixture fixture_wrapper.__name__ = func.__name__ fixture_wrapper.__doc__ = func.__doc__ # 绑定依赖参数列表,让Pytest自动注入对应Fixture fixture_wrapper._pytestfixturefunction = getattr(func, '_pytestfixturefunction', {}) fixture_wrapper._pytestfixturefunction['argnames'] = deps return fixture_wrapper return decorator # 将装饰器挂载到pytest模块,方便调用 pytest.maker = maker
2. 实际使用示例
定义基础Fixture后,即可按预期方式编写工厂Fixture:
# 基础依赖Fixture @pytest.fixture def fix1(): return "fixture_1" @pytest.fixture def fix2(): return "fixture_2" # 使用maker装饰器简化工厂Fixture @pytest.fixture() @pytest.maker("fix1", "fix2") def make_stuff(fix1_val, fix2_val, arg1, arg2): return f"{fix1_val}-{fix2_val}-{arg1}-{arg2}" # 测试用例 def test_me(make_stuff): result = make_stuff(1, 2) assert result == "fixture_1-fixture_2-1-2"
关键细节说明
- 依赖注入处理:
maker接收的依赖名称会被绑定到Fixture包装函数上,Pytest会自动解析并注入这些依赖到fixture_wrapper中。 - 元数据保留:必须保留原函数的名称、文档字符串以及Pytest Fixture的元数据,否则Pytest无法正确识别该Fixture。
- 参数合并逻辑:内部函数
inner会将Fixture依赖参数与测试时传入的参数合并,确保原函数能获取所有所需值。
进阶优化(可选)
如果想避免手动传入依赖名称,可以借助inspect模块通过参数命名约定自动识别依赖,比如约定以fix_开头的参数为Fixture依赖:
import pytest import inspect def maker(): def decorator(func): sig = inspect.signature(func) # 自动识别以fix_开头的参数作为Fixture依赖 dep_names = [name for name in sig.parameters if name.startswith("fix_")] def fixture_wrapper(**fixture_kwargs): # 提取依赖参数 dep_args = {k: v for k, v in fixture_kwargs.items() if k in dep_names} def inner(**func_kwargs): # 合并依赖与测试参数 all_kwargs = {**dep_args, **func_kwargs} return func(**all_kwargs) return inner fixture_wrapper.__name__ = func.__name__ fixture_wrapper._pytestfixturefunction = getattr(func, '_pytestfixturefunction', {}) fixture_wrapper._pytestfixturefunction['argnames'] = dep_names return fixture_wrapper return decorator pytest.maker = maker
使用时无需手动传入依赖列表,直接编写函数即可:
@pytest.fixture() @pytest.maker() def make_stuff(fix1, fix2, arg1, arg2): return f"{fix1}-{fix2}-{arg1}-{arg2}"
内容的提问来源于stack exchange,提问作者Frey Waid
相关产品推荐
相关产品推荐

