乐于分享
好东西不私藏

生成EXCEL格式测试用例

生成EXCEL格式测试用例

 前言
       在软件测试工作中,测试用例是保障测试质量、规范测试流程、提升测试效率的核心载体,而Excel格式因具备直观易用、结构清晰、可编辑性强、跨平台兼容等优势,成为测试用例最常用的呈现形式之一。无论是手动测试还是自动化测试的辅助场景,一份规范、完整的Excel测试用例,既能明确测试范围、测试步骤与预期结果,也能便于团队协作评审、测试过程追溯,以及后续的用例迭代与维护。

       以下是自动生成Excel格式测试用例demo,旨在解决手动编写测试用例耗时费力、格式不统一、易出现疏漏等问题,帮助测试人员或开发人员快速生成符合规范的测试用例模板,减少重复工作量,将更多精力投入到测试逻辑设计、用例优化等核心工作中。

代码示例
#!/usr/bin/env python3# -*- coding: utf-8 -*-"""Excel 测试用例生成器独立使用:from excel_generator import ExcelGenerator"""from typing import ListDictOptionalfrom pathlib import Pathtry:    from openpyxl import Workbook, load_workbook    from openpyxl.styles import Font, Alignment, PatternFill, Border, Side    from openpyxl.worksheet.datavalidation import DataValidationexcept ImportError:    raise ImportError("请安装 openpyxl: pip install openpyxl")# 默认配置DEFAULT_COLUMNS = ['用例编号''模块名称''用例标题''优先级''关联需求ID',                   '设计方法''前置条件''测试步骤''预期结果''实际结果',                   '是否通过''回归类型''备注']DEFAULT_WIDTHS = [1512288141022453525121018]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_COLUMNS        self.widths = widths or DEFAULT_WIDTHS        self.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.active        ws.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_path    def _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_path    def _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_font            cell.fill = header_fill            cell.alignment = Alignment(horizontal='center', vertical='center')            cell.border = border    def _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 = width    def _get_column_letter(self, col_num: int) -> str:        """将列号转换为字母(支持超过26列)"""        result = ""        while col_num > 0:            col_num -= 1            result = chr(65 + col_num % 26) + result            col_num //= 26        return result    def _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 = border    def _build_column_map(self, columns: List[str]) -> Dict[strint]:        """建立列名到索引的映射"""        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.columns        col_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.columns        col_map = {name: idx + 1 for idx, name in enumerate(col_list)}        if '优先级' not in col_map:            return        priority_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 添加需求追溯矩阵 Sheet        Args:            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)        # 创建追溯矩阵 Sheet        ws = 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_font            cell.fill = header_fill            cell.alignment = Alignment(horizontal='center', vertical='center')            cell.border = border        # 写入数据        row_idx = 2        for req_id, case_ids in sorted(req_case_map.items()):            case_count = len(case_ids)            covered = case_count > 0            ws.cell(row=row_idx, column=1, value=req_id).border = border            ws.cell(row=row_idx, column=2, value='').border = border            ws.cell(row=row_idx, column=3, value='').border = border            ws.cell(row=row_idx, column=4, value=', '.join(case_ids)).border = border            ws.cell(row=row_idx, column=5, value=case_count).border = border            status_cell = ws.cell(row=row_idx, column=6, value='✅ 已覆盖' if covered else '❌ 未覆盖')            status_cell.border = border            if not covered:                status_cell.fill = PatternFill(start_color="FFCDD2", end_color="FFCDD2", fill_type="solid")            row_idx += 1        # 设置列宽        widths = [142515401012]        for i, width in enumerate(widths):            ws.column_dimensions[self._get_column_letter(i + 1)].width = width        ws.freeze_panes = 'A2'        wb.save(excel_path)        return excel_path    def add_statistics_sheet(self, excel_path: str, cases: List[Dict],                             total_req_count: int = 0, covered_count: int = 0) -> str:        """        向现有 Excel 添加覆盖率统计 Sheet        Args:            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 = 0        for case in cases:            priority = str(case.get('优先级'or '').upper()            if priority in priority_counts:                priority_counts[priority] += 1            regression = case.get('回归类型'or ''            if regression in regression_counts:                regression_counts[regression] += 1            req_id = case.get('关联需求ID'or ''            if not req_id:                orphan_count += 1        coverage_rate = (covered_count / total_req_count * 100if total_req_count > 0 else 0        coverage_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_font                label_cell.fill = header_fill                value_cell.font = header_font                value_cell.fill = header_fill            label_cell.border = border            value_cell.border = border            label_cell.alignment = Alignment(horizontal='left', vertical='center')            value_cell.alignment = Alignment(horizontal='center', vertical='center')        ws.column_dimensions['A'].width = 18        ws.column_dimensions['B'].width = 20        wb.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. 点击登录按钮",            "预期结果""提示'用户名或密码错误'",            "回归类型""核心"        }    ]    # 生成 Excel    generator = 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("已添加覆盖率统计")