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.0、50.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 scene 且 tx_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()
文件查找优先级:
- 命令行参数指定路径(最高优先级)
- 当前目录下文件名含
PI(大写)的.xlsx文件 - 当前目录下任意
.xlsx文件 - 找不到则报错退出
四、完整运行示例
输入文件(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