如何在Python/Pytest中实现动态参数化,减少无效测试用例?
铁路物流系统测试用例参数化优化方案
问题背景
我正在为铁路物流系统编写测试用例,需传入多个JSON文件作为测试参数,但每个JSON对应不同测试场景(如放行载重/空车厢列车、检查站点车厢供应一致性等)。当前所有JSON会传入所有测试,导致大量无效测试用例被跳过,增加了不必要的复杂度。
当前测试代码示例
NODES_LIST = ['ECA', 'ECE', 'EDV', 'EYU', 'VEB', 'VIC', 'VOB', 'VTU'] SKIP_JSON = 'The test case file is not for the offer empty test.' SKIP_NODE = 'The test case file is for the offer empty test, but does not apply to this station.' @pytest.mark.parametrize('node_name', NODES_LIST) def test_offer_empty_consistency(input_json_filename: str, node_name: str): if ('offer_empty' not in input_json_filename): pytest.skip(SKIP_JSON) elif (((node_name in ['ECA', 'ECE', 'EDV', 'EYU', 'VEB', 'VOB']) and ('generic' not in input_json_filename)) or ((node_name in ['VIC', 'VTU']) and (node_name not in input_json_filename))): pytest.skip(SKIP_NODE) expected_offer_empty, really_offer_empty = offer_empty_consistency(input_json_filename=input_json_filename, node_name=node_name) assert expected_offer_empty == really_offer_empty, f'Failure.\nExpected empty offer values [{expected_offer_empty}] differ from actual values [{really_offer_empty}] for station {node_name}.'
期望的有效测试组合
以传入3个JSON文件['CT_01_offer_empty_generic', 'CT_02_offer_empty_VIC', 'CT_03_offer_empty_VTU']为例,仅需生成以下有效测试:
test_offer_empty_consistency[CT_01_offer_empty_generic-ECA] test_offer_empty_consistency[CT_01_offer_empty_generic-ECE] test_offer_empty_consistency[CT_01_offer_empty_generic-EDV] test_offer_empty_consistency[CT_01_offer_empty_generic-EYU] test_offer_empty_consistency[CT_01_offer_empty_generic-VEB] test_offer_empty_consistency[CT_01_offer_empty_generic-VOB] test_offer_empty_consistency[CT_02_offer_empty_VIC-VIC] test_offer_empty_consistency[CT_03_offer_empty_VTU-VTU]
尝试过的方案(存在问题)
曾尝试用类实现动态参数化,但node_name的参数化无法访问类实例的self变量:
@pytest.mark.parametrize('input_json_filename', test_related_lists.OFFER_EMPTY) class TestOfferEmpty: def __init__(self, input_json_filename) -> None: self.test_data = get_test_data(input_json_filename=input_json_filename) if 'generic' in input_json_filename: self.nodes = ['ECA', 'ECE', 'EDV', 'EYU', 'VEB', 'VOB'] elif 'VIC' in input_json_filename: self.nodes = ['VIC'] else: self.nodes = ['VTU'] @pytest.mark.parametrize('node_name', self.nodes) def test_offer_empty_consistency(self, node_name: str): expected_offer_empty, really_offer_empty = offer_empty_consistency(test_data=self.test_data, node_name=node_name) assert expected_offer_empty == really_offer_empty, f'Failure.\nExpected empty offer values [{expected_offer_empty}] differ from actual values [{really_offer_empty}] for station {node_name}.'
当前JSON文件通过conftest.py参数化传入,需实现仅传递相关JSON给对应测试,无需大量验证和跳过逻辑。
解决方案:自定义参数生成逻辑(基于pytest_generate_tests)
核心思路是在pytest_generate_tests中根据测试函数特性和JSON命名规则,提前生成仅包含有效组合的测试参数,而非先生成全量组合再跳过无效项。
步骤1:修改conftest.py的参数生成逻辑
更新conftest.py,针对不同测试函数生成对应有效参数组合:
import pytest from science_vli_qa.tests.utils import get_input_files_list def pytest_addoption(parser): parser.addoption("--input_json_filename", action="store", default=None) def get_valid_node_json_pairs(test_func_name, json_files): valid_pairs = [] # 针对空车厢供应一致性测试的规则 if test_func_name == "test_offer_empty_consistency": for json_file in json_files: if "offer_empty" not in json_file: continue # 跳过非对应场景的JSON # 根据JSON文件名匹配对应节点 if "generic" in json_file: nodes = ['ECA', 'ECE', 'EDV', 'EYU', 'VEB', 'VOB'] elif "VIC" in json_file: nodes = ['VIC'] elif "VTU" in json_file: nodes = ['VTU'] else: continue # 生成所有有效(json, node)组合,并设置测试ID for node in nodes: valid_pairs.append(pytest.param(json_file, node, id=f"{json_file}-{node}")) # 可扩展其他测试函数的参数规则 return valid_pairs def pytest_generate_tests(metafunc): # 获取指定或全量JSON文件列表 if option_value := metafunc.config.option.input_json_filename: params = option_value.split(",") json_files = [] for ct in params: formatted_ct_name = ct if ct.endswith('.json') else f'{ct}.json' json_files.append(formatted_ct_name) else: json_files = get_input_files_list() # 根据测试函数生成对应参数 test_func_name = metafunc.function.__name__ if test_func_name == "test_offer_empty_consistency": # 该测试需要两个参数:input_json_filename和node_name valid_pairs = get_valid_node_json_pairs(test_func_name, json_files) metafunc.parametrize("input_json_filename, node_name", valid_pairs) elif 'input_json_filename' in metafunc.fixturenames: # 其他仅需单个JSON参数的测试,沿用原有逻辑 metafunc.parametrize("input_json_filename", json_files)
步骤2:简化测试函数代码
移除所有跳过逻辑,仅保留核心测试逻辑:
def test_offer_empty_consistency(input_json_filename: str, node_name: str): expected_offer_empty, really_offer_empty = offer_empty_consistency( input_json_filename=input_json_filename, node_name=node_name ) assert expected_offer_empty == really_offer_empty, ( f'Failure.\nExpected empty offer values [{expected_offer_empty}] differ from actual values [{really_offer_empty}] for station {node_name}.' )
方案优势
- 无无效测试用例:参数生成阶段直接过滤无效组合,避免大量跳过操作
- 规则集中管理:所有JSON与测试、节点的映射规则集中维护,便于后续扩展其他测试场景
- 保留灵活性:依然支持通过
--input_json_filename指定特定JSON文件,自动生成对应有效节点组合
内容的提问来源于stack exchange,提问作者MadSweeney
相关产品推荐
相关产品推荐

