如何为不同导入路径定义可复用的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
相关产品推荐
相关产品推荐

