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

如何为不同导入路径定义可复用的pytest fixture?

解决方案

针对你遇到的重复打桩问题,有几种简洁的方法可以将fixture统一放到conftest.py中,无需重复编写:

方案1:打桩原始辅助函数(推荐)

既然所有业务模块都从app.helper导入do_thing,直接打桩原始模块的函数,再重新加载业务模块即可让所有引用生效:

# tests/conftest.py
import importlib
import app.module1
import app.module2
import pytest

def mock_do_thing():
    # 编写你的mock逻辑
    return "mocked result"

@pytest.fixture
def mock_helper_do_thing(monkeypatch):
    # 打桩原始的helper模块函数
    monkeypatch.setattr('app.helper.do_thing', mock_do_thing)
    # 重新加载业务模块,让它们使用mock后的函数引用
    importlib.reload(app.module1)
    importlib.reload(app.module2)
    yield
    # 可选:测试结束后恢复原始模块状态
    importlib.reload(app.module1)
    importlib.reload(app.module2)

测试文件直接使用该fixture即可:

# tests/test_module_1.py
def test_use_do_thing_for_x(mock_helper_do_thing):
    from app.module1 import use_do_thing_for_x
    result = use_do_thing_for_x()
    # 编写断言逻辑
# tests/test_module_2.py
def test_use_do_thing_for_y(mock_helper_do_thing):
    from app.module2 import use_do_thing_for_y
    result = use_do_thing_for_y()
    # 编写断言逻辑

原理

业务模块通过from helper import do_thing导入时,会在模块级别创建一个指向原始函数的引用。打桩原始模块的函数后,重新加载业务模块会让它们重新获取这个引用,从而指向mock版本。


方案2:动态传递模块路径

如果不想重新加载模块,可以让fixture接收模块名称参数,动态生成打桩路径:

# tests/conftest.py
import pytest

def mock_do_thing():
    # 编写你的mock逻辑
    return "mocked result"

@pytest.fixture
def setup_do_thing(monkeypatch, request):
    # 从测试参数中获取目标模块名
    target_module = request.param
    monkeypatch.setattr(f'app.{target_module}.do_thing', mock_do_thing)
    yield

测试文件通过参数化传递模块名:

# tests/test_module_1.py
import pytest

@pytest.mark.parametrize('setup_do_thing', ['module1'], indirect=True)
def test_use_do_thing_for_x(setup_do_thing):
    from app.module1 import use_do_thing_for_x
    use_do_thing_for_x()
    # 编写断言逻辑
# tests/test_module_2.py
import pytest

@pytest.mark.parametrize('setup_do_thing', ['module2'], indirect=True)
def test_use_do_thing_for_y(setup_do_thing):
    from app.module2 import use_do_thing_for_y
    use_do_thing_for_y()
    # 编写断言逻辑

方案3:自动推导业务模块

如果测试模块命名遵循test_<业务模块名>.py的规则,可以通过request对象自动推导要打桩的模块:

# tests/conftest.py
import pytest

def mock_do_thing():
    # 编写你的mock逻辑
    return "mocked result"

@pytest.fixture
def setup_do_thing(monkeypatch, request):
    # 从测试模块名称推导业务模块路径
    test_module_name = request.module.__name__  # 示例:"tests.test_module1"
    business_module_name = test_module_name.replace('tests.test_', 'app.')  # 转换为"app.module1"
    target_attr = f'{business_module_name}.do_thing'
    monkeypatch.setattr(target_attr, mock_do_thing)
    yield

测试文件直接使用fixture即可,无需额外配置:

# tests/test_module_1.py
def test_use_do_thing_for_x(setup_do_thing):
    from app.module1 import use_do_thing_for_x
    use_do_thing_for_x()
    # 编写断言逻辑

内容的提问来源于stack exchange,提问作者Tim Kirkwood

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 20:37:09