能否将返回列表的pytest fixture用作测试参数列表?
问题描述
我有一段抽象后的测试代码如下:
import os import pytest class Files: def __init__(self): self.file1 = "file1" self.file2 = "file2" self.file3 = "file3" self.file4 = "file4" self.file5 = "file5" class MyRunner: def __init__(self, files_obj): self.files = files_obj def get_files_to_copy(files_obj: Files): return [files_obj.file1, files_obj.file2, files_obj.file3] @pytest.fixture(scope="class") def runner(): """ long time to run... """ files_obj = Files() _runner = MyRunner(files_obj) return _runner @pytest.fixture(scope="class") def must_exist_files_list(runner): return get_files_to_copy(runner.files) @pytest.mark.parametrize("file", must_exist_files_list) def test_must_exist_files(file): assert os.path.exists(file), f"{file} does not exist"
我的需求是:runner fixture初始化耗时较长,且被其他测试复用;需要借助runner的files属性获取文件列表,用于test_must_exist_files测试。我尝试创建返回列表的must_exist_files_list fixture作为该测试的参数列表,但运行时报错:
TypeError: 'function' object is not iterable
请问有没有类似的实现方式?最优解决方案是什么?
解决方案
方案1:参数化文件属性名(推荐)
不需要额外fixture,直接对文件属性名做参数化,在测试中通过runner fixture获取对应文件路径。既复用了耗时的runner实例,又能让每个文件作为独立测试用例运行:
import os import pytest class Files: def __init__(self): self.file1 = "file1" self.file2 = "file2" self.file3 = "file3" self.file4 = "file4" self.file5 = "file5" class MyRunner: def __init__(self, files_obj): self.files = files_obj @pytest.fixture(scope="class") def runner(): """ long time to run... """ files_obj = Files() _runner = MyRunner(files_obj) return _runner @pytest.mark.parametrize("file_attr", ["file1", "file2", "file3"]) def test_must_exist_files(runner, file_attr): file_path = getattr(runner.files, file_attr) assert os.path.exists(file_path), f"{file_path} does not exist"
方案2:用pytest_generate_tests动态生成参数
如果文件列表需要从get_files_to_copy动态获取,不想硬编码属性名,可使用pytest_generate_tests钩子在测试收集阶段生成参数。注意要和原runner fixture的初始化逻辑保持一致,避免重复耗时操作:
import os import pytest class Files: def __init__(self): self.file1 = "file1" self.file2 = "file2" self.file3 = "file3" self.file4 = "file4" self.file5 = "file5" class MyRunner: def __init__(self, files_obj): self.files = files_obj def get_files_to_copy(files_obj: Files): return [files_obj.file1, files_obj.file2, files_obj.file3] @pytest.fixture(scope="class") def runner(): """ long time to run... """ files_obj = Files() _runner = MyRunner(files_obj) return _runner def pytest_generate_tests(metafunc): if "file" in metafunc.fixturenames: # 复用runner的初始化逻辑生成文件列表 files_obj = Files() runner_inst = MyRunner(files_obj) files_list = get_files_to_copy(runner_inst.files) metafunc.parametrize("file", files_list) def test_must_exist_files(runner, file): assert os.path.exists(file), f"{file} does not exist"
方案3:测试内遍历文件列表(简单但无独立用例)
如果不需要每个文件作为独立测试用例,可直接在测试中注入runner并遍历文件列表:
def test_must_exist_files(runner): files_list = get_files_to_copy(runner.files) for file in files_list: assert os.path.exists(file), f"{file} does not exist"
这种方式的缺点是:只要有一个文件不存在,测试会直接终止,无法看到剩余文件的检查结果。
内容的提问来源于stack exchange,提问作者noamgot
相关产品推荐
相关产品推荐

