generate_cases.py


generate_cases.py 代码讲解

一、脚本概述

generate_cases.py 自动化工具,用于将性能测试数据 Excel(Excel1)转换为标准测试案例清单 Excel(Excel2)

整体流程如下:

Excel1(性能总览数据)
        ↓
  读取 & 清洗数据
        ↓
  按场景类型生成案例
        ↓
Excel2(测试案例清单)

二、整体结构

脚本分为四个核心模块:

模块 函数 职责
固定模板内容 常量定义 存储各场景的步骤、预期值等固定文本
读取 Excel1 read_source_excel() 读取并清洗输入数据
生成案例列表 build_cases() 按规则将每行数据转换为案例
写出 Excel2 write_output_excel() 将案例列表写入带样式的 Excel

三、模块详解与案例演示

3.1 固定模板内容

脚本顶部定义了所有场景的固定文本常量,避免重复硬编码。

# 前置条件(通用)
PRECONDITION = (
    "1、应用、数据库、组件部署完成且正常运行\n"
    "2、脚本以及参数化文件、数据调试完成"
)

# 稳定性场景步骤模板({duration} 占位符运行时替换为实际小时数)
STEP_STABILITY_TPL = (
    "1.测试执行:按交易占比配置并发用户数,稳定执行{duration}小时,记录各交易在该并发下的平均响应时间、TPS;\n"
    ...
)

设计亮点: 稳定性场景使用了 {duration} 占位符,在运行时动态替换为实际测试时长,其余场景均为静态常量。


3.2 读取 Excel1:read_source_excel()

源码:

def read_source_excel(filepath):
    """读取性能总览 Excel,返回清洗后的 DataFrame"""
    # 自动选择引擎:xlsx 用 openpyxl,xls 用 xlrd
    engine = None
    if filepath.lower().endswith('.xlsx'):
        engine = 'openpyxl'
    elif filepath.lower().endswith('.xls'):
        engine = 'xlrd'

    df = pd.read_excel(filepath, sheet_name=0, header=0, engine=engine)
    df.columns = [str(c).strip() for c in df.columns]

    # 检查必要列
    for col in ["场景", "交易名", "并发数"]:
        if col not in df.columns:
            raise ValueError(
                "Excel1 中未找到列:{},请检查文件格式。实际列名:{}".format(col, list(df.columns))
            )

    # 去掉场景、交易名为空的行
    df = df.dropna(subset=["场景", "交易名"])
    df["场景"] = df["场景"].astype(str).str.strip()
    df["交易名"] = df["交易名"].astype(str).str.strip()

    # 并发数转整数字符串,避免 1.0、80.0 等小数形式
    def to_int_str(val):
        try:
            return str(int(float(val)))
        except (ValueError, TypeError):
            return str(val).strip()

    df["并发数"] = df["并发数"].apply(to_int_str)

    # 计算测试时长(小时),供稳定性场景使用
    def calc_duration_hours(row):
        try:
            start = pd.to_datetime(row.get("开始时间"))
            end = pd.to_datetime(row.get("结束时间"))
            if pd.isnull(start) or pd.isnull(end):
                return None
            hours = round((end - start).total_seconds() / 3600)
            return hours if hours > 0 else None
        except Exception:
            return None

    if "开始时间" in df.columns and "结束时间" in df.columns:
        df["_时长小时"] = df.apply(calc_duration_hours, axis=1)
    else:
        df["_时长小时"] = None

    return df

案例演示:

假设 Excel1 的内容如下:

场景 交易名 并发数 开始时间 结束时间
基准场景 用户登录 1
单交易拐点 查询余额 50
混合场景 TOTAL 80
稳定性场景 TOTAL 60 2026-04-01 09:00 2026-04-01 17:00

读取后,并发数 列中的 1.050.0 等浮点数会被自动转为 "1""50";稳定性场景行会额外计算出 _时长小时 = 8


3.3 生成案例列表:build_cases()

这是脚本的核心逻辑,通过场景名称关键字匹配,为每行数据生成对应的测试案例。

源码:

def build_cases(df):
    """
    根据规则从 DataFrame 生成案例列表。
    - 基准:每行(非TOTAL)生成一条
    - 单交易拐点:每行(非TOTAL)生成一条
    - 混合(不含容量/稳定):只取 TOTAL 行,每行生成一条
    - 容量(场景名含"容量"):只取 TOTAL 行,每行生成一条
    - 稳定性(场景名含"稳定"):只取 TOTAL 行,案例名称含时长
    """
    cases = []
    seq = 1

    for _, row in df.iterrows():
        scene = row["场景"]
        tx_name = row["交易名"]
        concurrency = row["并发数"]
        duration = row.get("_时长小时")

        # ---------- 基准场景 ----------
        if "基准" in scene:
            case_name = "moudles_{}_基准测试".format(tx_name)
            desc = "获取该交易在1并发情况下自身系统响应时间"
            step = STEP_BASELINE
            expected = EXPECTED_BASELINE

        # ---------- 单交易拐点场景 ----------
        elif "单交易" in scene:
            case_name = "moudles_{}_{}并发性能测试".format(tx_name, concurrency)
            desc = "获取该交易在单交易负载情况下的最佳性能表现"
            step = STEP_SINGLE
            expected = EXPECTED_SINGLE

        # ---------- 稳定性场景:场景名含"稳定",只处理 TOTAL 行 ----------
        elif "稳定" in scene:
            if tx_name.upper() != "TOTAL":
                continue
            dur_str = str(int(duration)) if duration else "N"
            case_name = "moudles_混合交易_{}_{}小时稳定性测试".format(concurrency, dur_str)
            desc = "稳定性场景采用混合交易拐点TPS 80%的压力执行{}小时,观察系统性能表现".format(dur_str)
            step = STEP_STABILITY_TPL.format(duration=dur_str)
            expected = EXPECTED_STABILITY

        # ---------- 容量场景:场景名含"容量",只处理 TOTAL 行 ----------
        elif "容量" in scene:
            if tx_name.upper() != "TOTAL":
                continue
            case_name = "moudles_混合交易_{}并发容量测试".format(concurrency)
            desc = "获取该系统在混合负载情况下的极限性能表现"
            step = STEP_CAPACITY
            expected = EXPECTED_CAPACITY

        # ---------- 混合场景:含"混合",不含"容量"/"稳定",只处理 TOTAL 行 ----------
        elif "混合" in scene:
            if tx_name.upper() != "TOTAL":
                continue
            case_name = "moudles_混合交易_{}并发性能测试".format(concurrency)
            desc = "获取该系统在混合负载情况下的最佳性能表现"
            step = STEP_MIXED
            expected = EXPECTED_MIXED

        else:
            continue  # 未知场景跳过

        cases.append({
            "案例id": "",
            "序号": seq,
            "案例名称": case_name,
            ...
        })
        seq += 1

    return cases

案例演示:

以下面四行输入数据为例,逐行说明生成逻辑:

案例 1:基准场景

输入行:

场景 交易名 并发数
基准场景 用户登录 1

匹配条件:"基准" in scene每行都生成(不过滤 TOTAL)

生成结果:

案例名称:moudles_用户登录_基准测试
描述:获取该交易在1并发情况下自身系统响应时间
步骤:
  1、配置测试场景:jmeter脚本设置并发数为1,执行时间为5分钟
  2、配置监控:监控应用、数据库以及各组件服务器资源使用和运行情况
  3、执行测试:启动压力脚本
  4、数据采集:通过jmeter统计响应时间的数据,截图保存
预期值:
  1、交易平均响应时间<指标值;
  2、交易成功率为100%

案例 2:单交易拐点场景

输入行:

场景 交易名 并发数
单交易拐点 查询余额 50

匹配条件:"单交易" in scene每行都生成

生成结果:

案例名称:moudles_查询余额_50并发性能测试
描述:获取该交易在单交易负载情况下的最佳性能表现

案例 3:混合场景(含非 TOTAL 行被过滤)

输入数据:

场景 交易名 并发数
混合场景 用户登录 80
混合场景 查询余额 80
混合场景 TOTAL 80

匹配条件:"混合" in scene只处理 TOTAL 行,前两行被 continue 跳过

生成结果(仅 1 条):

案例名称:moudles_混合交易_80并发性能测试
描述:获取该系统在混合负载情况下的最佳性能表现

案例 4:稳定性场景(时长自动计算)

输入行:

场景 交易名 并发数 开始时间 结束时间
稳定性场景 TOTAL 60 2026-04-01 09:00 2026-04-01 17:00

匹配条件:"稳定" in scenetx_name == "TOTAL" → 计算时长 = 8 小时

生成结果:

案例名称:moudles_混合交易_60_8小时稳定性测试
描述:稳定性场景采用混合交易拐点TPS 80%的压力执行8小时,观察系统性能表现
步骤中"执行{duration}小时" → 替换为"执行8小时"

若 Excel1 中没有开始/结束时间列,时长占位符为 "N",案例名称变为 moudles_混合交易_60并发_N小时稳定性测试


3.4 写出 Excel2:write_output_excel()

源码:

def write_output_excel(cases, output_path):
    """将案例列表写入 Excel"""
    wb = Workbook()
    ws = wb.active
    ws.title = "案例清单"

    headers = [
        "案例id", "序号", "案例名称", "描述", "应用系统",
        "优先级", "正/反", "前置条件", "备注", "步骤",
        "预期值", "创建人", "创建时间", "维护人", "维护时间",
        "案例目录", "案例类型", "案例标签"
    ]

    header_fill = PatternFill(fill_type="solid", fgColor="4472C4")
    header_font = Font(bold=True, color="FFFFFF")
    thin = Side(style="thin", color="000000")
    border = Border(left=thin, right=thin, top=thin, bottom=thin)

    # 第1行:表头(蓝底白字)
    for col_idx, h in enumerate(headers, start=1):
        cell = ws.cell(row=1, column=col_idx, value=h)
        cell.font = header_font
        cell.fill = header_fill
        cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
        cell.border = border

    # 第2行起:数据行
    for row_idx, case in enumerate(cases, start=2):
        for col_idx, h in enumerate(headers, start=1):
            val = case.get(h, "")
            cell = ws.cell(row=row_idx, column=col_idx, value=val)
            cell.alignment = Alignment(vertical="top", wrap_text=True)
            cell.border = border

    # 调整列宽(步骤、预期值列宽 65,案例名称列宽 50)
    col_widths = {
        1: 12, 2: 8, 3: 50, 4: 35, 5: 12,
        6: 8, 7: 8, 8: 35, 9: 12, 10: 65,
        11: 65, 12: 12, 13: 18, 14: 12, 15: 18,
        16: 15, 17: 15, 18: 15,
    }
    for col_idx, width in col_widths.items():
        ws.column_dimensions[ws.cell(row=1, column=col_idx).column_letter].width = width

    ws.freeze_panes = "A2"  # 冻结首行
    wb.save(output_path)
    print("生成完成,共 {} 条案例,输出文件:{}".format(len(cases), output_path))

输出效果说明:

  • 表头:蓝底(4472C4)白字加粗,居中对齐
  • 数据行:顶部对齐,自动换行,带细边框
  • 首行冻结,方便滚动查看
  • 步骤、预期值列宽设为 65,内容较长时自动换行显示

3.5 主入口:main()

源码:

def main():
    if len(sys.argv) >= 2:
        input_file = sys.argv[1]
    else:
        # 自动查找当前目录下包含 PI 的 xlsx 文件
        candidates = [
            f for f in os.listdir(".")
            if f.endswith(".xlsx") and "PI" in f.upper()
        ]
        if not candidates:
            candidates = [f for f in os.listdir(".") if f.endswith(".xlsx")]
        if not candidates:
            print("未找到输入文件,请通过命令行参数指定路径:")
            print("  python generate_cases.py <输入文件路径> [输出文件路径]")
            sys.exit(1)
        input_file = candidates[0]
        print("自动识别输入文件:{}".format(input_file))

    output_file = sys.argv[2] if len(sys.argv) >= 3 else "output_cases.xlsx"

    print("读取数据:{}".format(input_file))
    df = read_source_excel(input_file)
    print("  共读取 {} 行数据".format(len(df)))

    cases = build_cases(df)
    print("  生成案例数:{}".format(len(cases)))

    write_output_excel(cases, output_file)


if __name__ == "__main__":
    main()

文件查找优先级:

  1. 命令行参数指定路径(最高优先级)
  2. 当前目录下文件名含 PI(大写)的 .xlsx 文件
  3. 当前目录下任意 .xlsx 文件
  4. 找不到则报错退出

四、完整运行示例

输入文件(Excel1)示例数据

场景 交易名 并发数 开始时间 结束时间
基准场景 用户登录 1
基准场景 查询余额 1
单交易拐点 用户登录 30
单交易拐点 查询余额 50
混合场景 用户登录 80
混合场景 查询余额 80
混合场景 TOTAL 80
容量场景 用户登录 200
容量场景 TOTAL 200
稳定性场景 TOTAL 60 2026-04-01 09:00 2026-04-01 17:00

执行命令

# 方式一:指定输入输出文件
python generate_cases.py 性能测试数据_PI2026.xlsx output_cases.xlsx

# 方式二:自动识别(当前目录有含 PI 的 xlsx 文件)
python generate_cases.py

输出案例清单(Excel2)

序号 案例名称 描述
1 moudles_用户登录_基准测试 获取该交易在1并发情况下自身系统响应时间
2 moudles_查询余额_基准测试 获取该交易在1并发情况下自身系统响应时间
3 moudles_用户登录_30并发性能测试 获取该交易在单交易负载情况下的最佳性能表现
4 moudles_查询余额_50并发性能测试 获取该交易在单交易负载情况下的最佳性能表现
5 moudles_混合交易_80并发性能测试 获取该系统在混合负载情况下的最佳性能表现
6 moudles_混合交易_200并发容量测试 获取该系统在混合负载情况下的极限性能表现
7 moudles_混合交易_60并发8小时稳定性测试 稳定性场景采用混合交易拐点TPS 80%的压力执行8小时,观察系统性能表现

混合场景中,用户登录和查询余额两行(非 TOTAL)被自动过滤,只生成 1 条 TOTAL 对应的案例。


五、场景匹配规则总结

场景关键字 处理行 案例名称格式
基准 所有非空行 moudles_{交易名}_基准测试
单交易 所有非空行 moudles_{交易名}_{并发数}并发性能测试
混合(不含容量/稳定) 仅 TOTAL 行 moudles_混合交易_{并发数}并发性能测试
容量 仅 TOTAL 行 moudles_混合交易_{并发数}并发容量测试
稳定 仅 TOTAL 行 moudles_混合交易_{并发数}_{时长}小时稳定性测试
其他 跳过

匹配顺序为 if / elif 链,优先级从上到下:基准 → 单交易 → 稳定 → 容量 → 混合。


六、依赖安装

pip install pandas openpyxl
# 如需读取旧版 .xls 文件,还需安装:
pip install xlrd

  目录