贝利信息

如何优雅地消除 Pytest 测试中的重复代码

日期:2026-01-09 00:00 / 作者:心靈之曲

本文介绍通过参数化测试与抽象断言逻辑,将健康/故障两类 mape 测试合并为单一、可维护的 pytest 测试函数,避免硬编码路径和重复调用 calculate_mape_range。

在大型测试套件中,类似 TestMapeHealthy 和 TestMapeFaulty 这样仅因 YAML 路径键名(如 'healthy_test_list' vs 'faulty_test_list')和断言期望值(== 0 vs > 0)而复制整段测试逻辑的情况,不仅增加维护成本,还易引入不一致缺陷。Pytest 提供了强大而优雅的解决方案:多维度参数化 + 行为抽象

核心思路是将差异点提取为参数:

以下是重构后的推荐写法:

import pytest

@pytest.mark.parametrize("threshold", THRESHOLD_COMPREHENSION)
@pytest.mark.parametrize("window_size", WINDOW_SIZE_COMPREHENSION)
@pytest.mark.parametrize(
    "final_key, should_pass",
    [
        ("healthy_test_list", True),
        ("faulty_test_list", False),
    ],
    ids=["healthy", "faulty"]  # 可选:提升测试报告可读性
)
def test_MAPE(
    self,
    threshold: float,
    window_size: int,
    final_key: str,
    should_pass: bool,
    load_config: dict
):
    # 统一提取 YAML 中的测试文件 ID 对
    test_ids = load_config['test_plan']['test_ids'][VERSION_TAG]['tools']['test_file_ids']
    path_1 = test_ids[final_key][0]
    path_2 = test_ids[final_key][1]

    # 复用核心计算逻辑
    consecutive_failures = self.calculate_mape_range(path_1, path_2, window_size, threshold)

    # 根据 should_pass 动态断言
    if should_pass:
        assert consecutive_failures == 0, (
            f"Expected no consecutive failures for {final_key} with "
            f"threshold={threshold}, window={window_size}, but got {consecutive_failures}"
        )
    else:
        assert consecutive_failures > 0, (
            f"Expected at least one consecutive failure for {final_key} with "
            f"threshold={threshold}, window={window_size}, but got {consecutive_failures}"
        )

优势说明

⚠️ 注意事项

通过这一重构,你不仅消除了 90% 的样板代码,更构建了一个面向变化、易于演进的测试架构——这正是高质量自动化测试的核心追求。