使用pytest.mark.parametrize动态生成测试参数的问题
pytest 的 @pytest.mark.parametrize 是在测试用例收集阶段就完成参数解析的,而 fixture 要等到测试执行阶段才会运行。所以你第一个例子里,fixture 修改 data 的操作是在参数化之后才执行的,自然不会影响测试用例的数量。
直接把 fixture 传给 parametrize 会报错,是因为 parametrize 需要接收可迭代的参数组,而 fixture 本身是函数对象,不符合要求。
至于你用 obtain_data() 的方式报错,大概率是函数返回的 data 为空,或者格式不符合要求(比如不是 (arg0, arg1) 这种元组组成的列表)。
方案一:静态动态生成(不依赖 fixture)
如果测试数据只是需要动态生成(比如读取文件、数据库),不需要依赖其他 fixture,只需确保生成函数返回非空、格式正确的可迭代对象即可:
def obtain_data(): data = [] # 模拟读取1000条样本并预处理 for i in range(1000): data.append((str(i), str(i+1))) return data @pytest.mark.parametrize("arg0, arg1", obtain_data()) def test_data(arg0, arg1): print(arg0, arg1) assert True
方案二:运行时动态生成(依赖 fixture 或实时数据)
如果数据必须在测试执行阶段生成(比如依赖其他 fixture 的结果、实时获取数据),可以用 pytest 的 pytest_generate_tests 钩子函数——它会在测试收集完成后、执行前运行,能动态生成参数:
def pytest_generate_tests(metafunc): # 只针对需要参数化的测试函数处理 if "arg0" in metafunc.fixturenames and "arg1" in metafunc.fixturenames: # 这里可以写任意动态生成数据的逻辑,包括调用其他fixture的逻辑 data = [("1", "2")] data.append(("3", "4")) # 动态添加参数化配置 metafunc.parametrize("arg0, arg1", data) class Tester: def test_data(self, arg0, arg1): print(arg0, arg1) print(data) assert 0
如果需要依赖其他 fixture 的结果(比如某个配置 fixture),可以通过 metafunc.config 提前获取配置,再生成测试数据。
方案三:间接参数化(适合参数需要 fixture 处理的场景)
如果参数本身需要由 fixture 加工处理,可以用 indirect=True 让参数传给 fixture 处理,再返回给测试用例:
@pytest.fixture def process_data(request): # request.param 是传入的原始参数 base_arg = request.param # 模拟加工生成多组参数 return [(base_arg[0], base_arg[1]), ("3", "4")] @pytest.mark.parametrize("process_data", [("1", "2")], indirect=True) def test_data(process_data): # 这里会拿到fixture返回的多组参数,可循环执行断言 for arg0, arg1 in process_data: print(arg0, arg1) assert 0
注意:这种方式是在一个测试用例里循环执行,不会生成多个独立的测试用例;如果需要每个参数组作为单独的测试用例,优先用方案二。
内容的提问来源于stack exchange,提问作者Gerry

