pytest.parametrize 不支持动态函数对象,需传函数名字符串并在测试内用 globals() 获取;动态类同理,应传标识符并在测试中构造;复杂场景用 pytest_generate_tests hook。

pytest.parametrize 里传入动态生成的函数名会报错
直接在 @pytest.mark.parametrize 的 argnames 或 argvalues 里写 getattr(module, func_name) 是不行的——pytest 在收集测试阶段(import 阶段)就解析装饰器参数,此时模块可能还没 fully loaded,或 func_name 是运行时才确定的字符串,导致 AttributeError 或 NameError。
常见错误现象:TypeError: ,或收集阶段直接崩溃,提示无法序列化动态对象。
- parametrize 的
argvalues必须是静态可序列化的结构(list/tuple/dict 字面量、基本类型、pytest.param()实例) - 函数对象本身不能作为
argvalues元素直接传入;但可以传它的名字(字符串),然后在测试函数体内用globals()或getattr()动态获取 - 如果函数在测试文件内定义,优先用
globals()[func_name];跨模块则确保模块已导入且作用域可达
用字符串函数名 + globals() 在测试体内调用最稳妥
把“动态性”从装饰器移到测试函数内部,既绕过 pytest 收集期限制,又保持逻辑清晰。适用于:同一组测试逻辑要覆盖多个动态构造的函数(比如插件系统中按名称加载的处理器)。
示例场景:你有一批以 handler_v1、handler_v2 命名的函数,它们在运行时才注册进全局命名空间:
def test_handler_by_name(request):
handler_name = request.param
handler_func = globals().get(handler_name)
assert handler_func is not None, f"no handler named {handler_name}"
result = handler_func("test_input")
assert result == "expected"
对应 parametrize 写法:
@pytest.mark.parametrize("request", ["handler_v1", "handler_v2"], indirect=True)
def test_handler_by_name(request):
# 如上...
- 必须加
indirect=True,否则 pytest 会把字符串当普通参数传,而不是触发 fixture 查找 - 更推荐显式命名参数,比如
@pytest.mark.parametrize("handler_name", ["handler_v1", "handler_v2"]),然后在函数签名里写def test_handler_by_name(handler_name):,更直白 - 避免在
globals()中查找不到时静默失败,务必加assert或raise
需要动态生成整个测试函数?用 pytest_generate_tests hook
当 parametrize 不够用——比如你要为每个 YAML 测试用例文件自动生成一个独立 test 函数,且函数名要包含文件名,这时就得介入 pytest 的收集流程。
在 conftest.py 里定义:
def pytest_generate_tests(metafunc):
if "case_data" in metafunc.fixturenames:
test_files = list(Path("tests/cases").glob("*.yml"))
ids = [f.name for f in test_files]
metafunc.parametrize("case_data", test_files, ids=ids)
然后在测试文件里写:
def test_from_yaml(case_data):
data = yaml.safe_load(case_data.read_text())
func = globals()[data["handler"]]
assert func(**data["input"]) == data["expected"]
- 这个 hook 在 pytest 收集测试前执行,能读文件、import 模块、甚至 importlib.reload,自由度高
-
ids参数决定 pytest -v 输出的测试名,不设的话默认显示索引,很难 debug - 注意路径是相对于当前 pytest 执行目录(通常是项目根目录),不是
conftest.py所在目录
动态类怎么测?别 parametrize 类本身,parametrize 类名或配置
对动态生成的类(比如用 type() 或 __new__ 构造),同样不能把类对象塞进 @pytest.mark.parametrize。正确做法是传构造参数或类标识符,在测试里实例化。
例如你有工厂函数 make_processor(kind: str) -> Type[BaseProcessor]:
@pytest.mark.parametrize("kind", ["json", "xml", "csv"])
def test_processor_output(kind):
ProcessorClass = make_processor(kind)
proc = ProcessorClass()
assert proc.process("raw") == "processed"
- 只要
make_processor是纯函数、无副作用、返回确定类型,就可以安全调用 - 如果类构造需要复杂上下文(如数据库连接),建议把初始化逻辑提取到 fixture,而不是硬塞进测试函数体
- 避免在 parametrize 里调用
type(...)生成类——每次调用都产生新类对象,pytest 可能无法稳定识别,且不利于缓存和复用
真正麻烦的是带闭包或依赖局部变量的动态类,那种情况通常说明设计已偏离可测试性边界,得重构——不是 pytest 的问题,是代码组织的问题。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











