使用pytest能否编写真正的通用测试用例而非参数化测试用例?
动态生成独立命名的测试用例
场景与问题
测试目录结构如下:
$ tree tests tests ├── scenario1 ├── scenario2 └── scenario3
最初的写法是在单个函数中遍历执行所有场景,所有场景会被当作一个测试项;使用@pytest.mark.parametrize优化后,场景会作为参数依附于test_generic,输出如下:
collected 3 items test_me.py::test_generic[scenario1] PASSED test_me.py::test_generic[scenario3] PASSED test_me.py::test_generic[scenario2] PASSED
需要实现的效果:让每个scenario成为独立的测试用例,输出类似:
collected 3 items test_me.py::scenario1 PASSED test_me.py::scenario3 PASSED test_me.py::scenario2 PASSED
解决方案
步骤1:动态生成测试函数
在测试文件(如test_me.py)中编写代码,遍历场景目录并生成独立的测试函数:
from pathlib import Path import pytest def execute_test(scenario): # 这里编写你的测试执行逻辑 # 示例:检查场景目录存在 assert Path(f"tests/{scenario}").exists() # 遍历tests目录下的所有场景 tests_dir = Path("tests") for scenario_dir in tests_dir.iterdir(): if scenario_dir.is_dir(): scenario_name = scenario_dir.name # 生成测试函数的闭包,避免循环变量引用问题 def create_test_func(scenario): def test_func(): execute_test(scenario) return test_func # 命名测试函数(pytest要求函数以test_开头) test_func_name = f"test_{scenario_name}" # 将测试函数添加到当前模块 globals()[test_func_name] = create_test_func(scenario_name)
步骤2:修改测试用例显示名称
在conftest.py中添加钩子,将测试函数名的test_前缀去掉,让显示名称直接为场景名:
def pytest_itemcollected(item): # 仅处理我们动态生成的测试函数 if item.name.startswith("test_scenario"): # 提取场景名 scenario_name = item.name.replace("test_", "") # 修改测试节点的显示ID item._nodeid = item._nodeid.replace(item.name, scenario_name)
运行效果
执行pytest后,输出会符合预期:
collected 3 items test_me.py::scenario1 PASSED test_me.py::scenario2 PASSED test_me.py::scenario3 PASSED
拓展:场景本身支持参数化
如果需要让单个场景也支持参数化,可以在动态生成测试函数时,为每个场景添加@pytest.mark.parametrize装饰器。例如:
from pathlib import Path import pytest def execute_test(scenario, param): # 带参数的测试逻辑 print(f"Running {scenario} with param: {param}") assert param > 0 tests_dir = Path("tests") for scenario_dir in tests_dir.iterdir(): if scenario_dir.is_dir(): scenario_name = scenario_dir.name # 假设每个场景有自己的参数列表,可从场景目录的配置文件读取 params = [1, 2, 3] def create_test_func(scenario): @pytest.mark.parametrize("param", params, ids=lambda x: f"param{x}") def test_func(param): execute_test(scenario, param) return test_func test_func_name = f"test_{scenario_name}" globals()[test_func_name] = create_test_func(scenario_name)
此时运行pytest会输出类似:
collected 9 items test_me.py::scenario1[param1] PASSED test_me.py::scenario1[param2] PASSED test_me.py::scenario1[param3] PASSED test_me.py::scenario2[param1] PASSED ...
内容的提问来源于stack exchange,提问作者frans
相关产品推荐
相关产品推荐

