生成EXCEL格式测试用例
以下是自动生成Excel格式测试用例demo,旨在解决手动编写测试用例耗时费力、格式不统一、易出现疏漏等问题,帮助测试人员或开发人员快速生成符合规范的测试用例模板,减少重复工作量,将更多精力投入到测试逻辑设计、用例优化等核心工作中。
#!/usr/bin/env python3# -*- coding: utf-8 -*-"""Excel 测试用例生成器独立使用:from excel_generator import ExcelGenerator"""from typing import List, Dict, Optionalfrom pathlib import Pathtry:from openpyxl import Workbook, load_workbookfrom openpyxl.styles import Font, Alignment, PatternFill, Border, Sidefrom openpyxl.worksheet.datavalidation import DataValidationexcept ImportError:raise ImportError("请安装 openpyxl: pip install openpyxl")# 默认配置DEFAULT_COLUMNS = ['用例编号', '模块名称', '用例标题', '优先级', '关联需求ID','设计方法', '前置条件', '测试步骤', '预期结果', '实际结果','是否通过', '回归类型', '备注']DEFAULT_WIDTHS = [15, 12, 28, 8, 14, 10, 22, 45, 35, 25, 12, 10, 18]PRIORITY_COLORS = {'P0': 'FF0000', # Red'P1': 'FF6600', # Orange'P2': 'FFCC00', # Yellow'P3': '99CC00', # Green}REGRESSION_TYPES = ['冒烟', '核心', '全量']PASS_STATUS = ['通过', '未通过', '阻塞', '未执行']class ExcelGenerator:"""Excel 测试用例生成器"""def __init__(self,columns: List[str] = None,widths: List[int] = None,template_path: str = None):"""初始化 Excel 生成器Args:columns: 自定义列名(可选)widths: 自定义列宽(可选)template_path: 模板文件路径(可选)"""self.columns = columns or DEFAULT_COLUMNSself.widths = widths or DEFAULT_WIDTHSself.template_path = template_path# 确保列数和宽度数匹配if len(self.widths) < len(self.columns):self.widths.extend([12] * (len(self.columns) - len(self.widths)))def generate(self,cases: List[Dict],output_path: str,sheet_name: str = "测试用例") -> str:"""生成 Excel 文件Args:cases: 测试用例列表,每个用例是一个字典output_path: 输出文件路径sheet_name: 工作表名称Returns:输出文件路径"""if self.template_path and Path(self.template_path).exists():return self._generate_from_template(cases, output_path, sheet_name)else:return self._generate_new(cases, output_path, sheet_name)def _generate_new(self, cases: List[Dict], output_path: str, sheet_name: str) -> str:"""从零生成 Excel"""wb = Workbook()ws = wb.activews.title = sheet_name# 写入表头self._write_header(ws)# 设置列宽self._set_column_widths(ws)# 写入数据self._write_data(ws, cases)# 添加数据验证self._add_data_validation(ws, len(cases) + 1)# 应用优先级颜色self._apply_priority_colors(ws, len(cases) + 1)# 冻结首行ws.freeze_panes = 'A2'wb.save(output_path)return output_pathdef _generate_from_template(self, cases: List[Dict], output_path: str, sheet_name: str) -> str:"""基于模板生成 Excel"""wb = load_workbook(self.template_path)ws = wb.active# 获取模板中的列名template_columns = []for cell in ws[1]:if cell.value:template_columns.append(str(cell.value).strip())# 清空模板中的示例数据for row in ws.iter_rows(min_row=2, max_row=ws.max_row):for cell in row:cell.value = None# 建立列映射col_map = self._build_column_map(template_columns)# 写入数据border = Border(left=Side(style='thin'), right=Side(style='thin'),top=Side(style='thin'), bottom=Side(style='thin'))for row_idx, case in enumerate(cases, 2):for key, value in case.items():col_num = col_map.get(key)if col_num:cell = ws.cell(row=row_idx, column=col_num, value=value)cell.alignment = Alignment(vertical='top', wrap_text=True)cell.border = border# 添加数据验证self._add_data_validation(ws, len(cases) + 1, template_columns)wb.save(output_path)return output_pathdef _write_header(self, ws):"""写入表头"""header_font = Font(bold=True, color="FFFFFF", size=11)header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")border = Border(left=Side(style='thin'), right=Side(style='thin'),top=Side(style='thin'), bottom=Side(style='thin'))for col, name in enumerate(self.columns, 1):cell = ws.cell(row=1, column=col, value=name)cell.font = header_fontcell.fill = header_fillcell.alignment = Alignment(horizontal='center', vertical='center')cell.border = borderdef _set_column_widths(self, ws):"""设置列宽"""for i, width in enumerate(self.widths):if i < len(self.columns):col_letter = self._get_column_letter(i + 1)ws.column_dimensions[col_letter].width = widthdef _get_column_letter(self, col_num: int) -> str:"""将列号转换为字母(支持超过26列)"""result = ""while col_num > 0:col_num -= 1result = chr(65 + col_num % 26) + resultcol_num //= 26return resultdef _write_data(self, ws, cases: List[Dict]):"""写入数据"""border = Border(left=Side(style='thin'), right=Side(style='thin'),top=Side(style='thin'), bottom=Side(style='thin'))# 建立列索引映射col_map = {col_name: idx + 1 for idx, col_name in enumerate(self.columns)}for row_idx, case in enumerate(cases, 2):for key, value in case.items():col_num = col_map.get(key)if col_num:cell = ws.cell(row=row_idx, column=col_num, value=value)cell.alignment = Alignment(vertical='top', wrap_text=True)cell.border = borderdef _build_column_map(self, columns: List[str]) -> Dict[str, int]:"""建立列名到索引的映射"""return {col: idx + 1 for idx, col in enumerate(columns)}def _add_data_validation(self, ws, max_row: int, columns: List[str] = None):"""添加数据验证(下拉列表)"""col_list = columns or self.columnscol_map = {name: idx + 1 for idx, name in enumerate(col_list)}# 优先级下拉if '优先级' in col_map:priority_col = col_map['优先级']priority_dv = DataValidation(type="list", formula1='"P0,P1,P2,P3"', allow_blank=True)ws.add_data_validation(priority_dv)for row in range(2, max_row):priority_dv.add(ws.cell(row=row, column=priority_col))# 回归类型下拉if '回归类型' in col_map:regression_col = col_map['回归类型']regression_dv = DataValidation(type="list", formula1='"冒烟,核心,全量"', allow_blank=True)ws.add_data_validation(regression_dv)for row in range(2, max_row):regression_dv.add(ws.cell(row=row, column=regression_col))# 是否通过下拉if '是否通过' in col_map:pass_col = col_map['是否通过']pass_dv = DataValidation(type="list", formula1='"通过,未通过,阻塞,未执行"', allow_blank=True)ws.add_data_validation(pass_dv)for row in range(2, max_row):pass_dv.add(ws.cell(row=row, column=pass_col))def _apply_priority_colors(self, ws, max_row: int, columns: List[str] = None):"""根据优先级设置单元格颜色"""col_list = columns or self.columnscol_map = {name: idx + 1 for idx, name in enumerate(col_list)}if '优先级' not in col_map:returnpriority_col = col_map['优先级']for row in range(2, max_row):cell = ws.cell(row=row, column=priority_col)priority = str(cell.value).upper() if cell.value else ''if priority in PRIORITY_COLORS:cell.fill = PatternFill(start_color=PRIORITY_COLORS[priority],end_color=PRIORITY_COLORS[priority],fill_type="solid")if priority in ['P0', 'P1']:cell.font = Font(bold=True, color="FFFFFF")def add_traceability_sheet(self, excel_path: str, cases: List[Dict],requirements: List[Dict] = None) -> str:"""向现有 Excel 添加需求追溯矩阵 SheetArgs:excel_path: Excel 文件路径cases: 测试用例列表requirements: 需求列表(可选)Returns:Excel 文件路径"""wb = load_workbook(excel_path)# 构建需求-用例映射req_case_map = {}for case in cases:req_id = case.get('关联需求ID') or case.get('req_id') or ''if req_id:req_ids = [r.strip() for r in str(req_id).split(',')]for rid in req_ids:if rid:if rid not in req_case_map:req_case_map[rid] = []case_id = case.get('用例编号') or case.get('id') or ''if case_id:req_case_map[rid].append(case_id)# 创建追溯矩阵 Sheetws = wb.create_sheet(title="需求追溯矩阵")# 写入表头headers = ['需求ID', '需求名称', '所属模块', '关联用例', '用例数量', '覆盖状态']header_font = Font(bold=True, color="FFFFFF", size=11)header_fill = PatternFill(start_color="2E7D32", end_color="2E7D32", fill_type="solid")border = Border(left=Side(style='thin'), right=Side(style='thin'),top=Side(style='thin'), bottom=Side(style='thin'))for col, name in enumerate(headers, 1):cell = ws.cell(row=1, column=col, value=name)cell.font = header_fontcell.fill = header_fillcell.alignment = Alignment(horizontal='center', vertical='center')cell.border = border# 写入数据row_idx = 2for req_id, case_ids in sorted(req_case_map.items()):case_count = len(case_ids)covered = case_count > 0ws.cell(row=row_idx, column=1, value=req_id).border = borderws.cell(row=row_idx, column=2, value='').border = borderws.cell(row=row_idx, column=3, value='').border = borderws.cell(row=row_idx, column=4, value=', '.join(case_ids)).border = borderws.cell(row=row_idx, column=5, value=case_count).border = borderstatus_cell = ws.cell(row=row_idx, column=6, value='✅ 已覆盖' if covered else '❌ 未覆盖')status_cell.border = borderif not covered:status_cell.fill = PatternFill(start_color="FFCDD2", end_color="FFCDD2", fill_type="solid")row_idx += 1# 设置列宽widths = [14, 25, 15, 40, 10, 12]for i, width in enumerate(widths):ws.column_dimensions[self._get_column_letter(i + 1)].width = widthws.freeze_panes = 'A2'wb.save(excel_path)return excel_pathdef add_statistics_sheet(self, excel_path: str, cases: List[Dict],total_req_count: int = 0, covered_count: int = 0) -> str:"""向现有 Excel 添加覆盖率统计 SheetArgs:excel_path: Excel 文件路径cases: 测试用例列表total_req_count: 总需求数covered_count: 已覆盖需求数Returns:Excel 文件路径"""wb = load_workbook(excel_path)ws = wb.create_sheet(title="覆盖率统计")# 计算统计数据total_cases = len(cases)priority_counts = {'P0': 0, 'P1': 0, 'P2': 0, 'P3': 0}regression_counts = {'冒烟': 0, '核心': 0, '全量': 0}orphan_count = 0for case in cases:priority = str(case.get('优先级') or '').upper()if priority in priority_counts:priority_counts[priority] += 1regression = case.get('回归类型') or ''if regression in regression_counts:regression_counts[regression] += 1req_id = case.get('关联需求ID') or ''if not req_id:orphan_count += 1coverage_rate = (covered_count / total_req_count * 100) if total_req_count > 0 else 0coverage_depth = (total_cases / total_req_count) if total_req_count > 0 else 0# 写入统计数据stats = [('统计项', '数值'),('总需求数', total_req_count),('已覆盖需求数', covered_count),('未覆盖需求数', total_req_count - covered_count),('需求覆盖率', f'{coverage_rate:.1f}%'),('', ''),('总用例数', total_cases),('覆盖深度', f'{coverage_depth:.2f} 用例/需求'),('孤儿用例数', orphan_count),('', ''),('P0 用例数', priority_counts['P0']),('P1 用例数', priority_counts['P1']),('P2 用例数', priority_counts['P2']),('P3 用例数', priority_counts['P3']),('', ''),('冒烟测试用例', regression_counts['冒烟']),('核心回归用例', regression_counts['核心']),('全量回归用例', regression_counts['全量']),]header_font = Font(bold=True, color="FFFFFF", size=11)header_fill = PatternFill(start_color="1565C0", end_color="1565C0", fill_type="solid")border = Border(left=Side(style='thin'), right=Side(style='thin'),top=Side(style='thin'), bottom=Side(style='thin'))for row_idx, (label, value) in enumerate(stats, 1):label_cell = ws.cell(row=row_idx, column=1, value=label)value_cell = ws.cell(row=row_idx, column=2, value=value)if row_idx == 1:label_cell.font = header_fontlabel_cell.fill = header_fillvalue_cell.font = header_fontvalue_cell.fill = header_filllabel_cell.border = bordervalue_cell.border = borderlabel_cell.alignment = Alignment(horizontal='left', vertical='center')value_cell.alignment = Alignment(horizontal='center', vertical='center')ws.column_dimensions['A'].width = 18ws.column_dimensions['B'].width = 20wb.save(excel_path)return excel_path# 独立使用示例if __name__ == "__main__":# 示例数据sample_cases = [{"用例编号": "TC_001","模块名称": "登录模块","用例标题": "正常登录验证","优先级": "P0","关联需求ID": "REQ_LOGIN_001","设计方法": "ST","前置条件": "用户已注册","测试步骤": "1. 打开登录页面\n2. 输入正确的用户名和密码\n3. 点击登录按钮","预期结果": "登录成功,跳转到首页","回归类型": "核心"},{"用例编号": "TC_002","模块名称": "登录模块","用例标题": "密码错误验证","优先级": "P1","关联需求ID": "REQ_LOGIN_002","设计方法": "BVA","前置条件": "用户已注册","测试步骤": "1. 打开登录页面\n2. 输入正确的用户名,错误的密码\n3. 点击登录按钮","预期结果": "提示'用户名或密码错误'","回归类型": "核心"}]# 生成 Excelgenerator = ExcelGenerator()generator.generate(sample_cases, "./sample_testcases.xlsx")print("Excel 生成成功: ./sample_testcases.xlsx")# 添加追溯矩阵generator.add_traceability_sheet("./sample_testcases.xlsx", sample_cases)print("已添加需求追溯矩阵")# 添加统计generator.add_statistics_sheet("./sample_testcases.xlsx", sample_cases, total_req_count=5, covered_count=2)print("已添加覆盖率统计")
夜雨聆风