You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 05:32:46