公共用例库计划--个人版(七)Excel导入功能开发与导出功能改造

39 篇文章 52 订阅

1、任务概述

  本次计划的核心任务是开发一个,个人版的公共用例库,旨在将各系统和各类测试场景下的通用、基础以及关键功能的测试用例进行系统性地归纳整理,并以提高用例的复用率为目标,力求最大限度地减少重复劳动,提升测试效率。
  计划内容:完成公共用例库的开发实施工作,包括需求分析、系统设计、开发、测试、打包、运行维护等工作。

1.1、 已完成:

  需求分析、数据库表的设计:公共用例库计划–个人版(一)
  主体界面与逻辑设计:公共用例库计划–个人版(二)
  导出Excel功能:公共用例库计划–个人版(三)
  模块选择功能改造与性能优化公共用例库计划–个人版(四)
  QtCharts制作首页饼图与柱状图公共用例库计划–个人版(五)
  典型Bug页面设计与开发公共用例库计划–个人版(六)

1.2、 本次待完成:

导入导出页面:选择模块,把用例导出为Excel文件。导入:模板中填写用例与模块信息,导入库中。
  1. 导出模板改造
  2. Excel导入用例

导入导出完成后,公共用例库的主体功能就开发完成了。剩下部分功能的优化,还有测试。同时进行界面的美化,打包。

2、导出功能改造

主要用到的库

	PyQt6
	openpyxl
	datetime

2.1 界面

  界面增加了按钮图标。还是点击选择模块,选择路径后,点击导出。

在这里插入图片描述

2.2 导出bug修改

1、bug情况描述:选择模块后查询,页面显示没有模块选中,点击导出却导出了用例。
  分析:是模块查询没有清空已选模块的ID集合,导出了查询前的选择模块。
在这里插入图片描述
  解决:将self.selected_module_values集合定义,从页面初始化移动到模块查询中。这样页面初始化会查询模块,可以定义集合,查询操作也可以清空集合。

    def mkchaxun_down(self):
        """导出,模块查询"""
        self.selected_module_values=set()  # 导出模块选择
        vlue=('%' + self.mkcx_3.text() + '%', 10)
        row, count=self.mk_selecetsql(vlue)
        self.mkpailie(row, self.mkliebiao_3)
        self.label_19.setText(f"模块数: {count[0]}")
        logging.info('导出,模块查询')

2、bug情况描述:点击全选后,一个取消选中,但是模块全选还是勾选。
  分析:取消选中只删除了选中模块ID,没有对全选checkBox进行重置。
在这里插入图片描述  解决:在取消选中,删除模块ID的语句后面,增加:self.checkBox_2.setChecked(False) # 取消全选

    def on_item_clicked_down(self, item, column):
        """导出功能,模块及其子模块选中或者取消选中"""
        parent_check_state=Qt.CheckState.Unchecked if item.checkState(
            column) == Qt.CheckState.Checked else Qt.CheckState.Checked
        item.setCheckState(column, parent_check_state)  # 设置当前项的勾选状态
        if item.checkState(column) == Qt.CheckState.Checked:
            self.selected_module_values.add(int(item.text(1)))  # 如果当前项被选中,则添加到 selected_module_values 集合
        else:
            try:
                self.selected_module_values.remove(int(item.text(1)))  # 如果当前项被取消选中,则从 selected_module_values 集合中移除
                self.checkBox_2.setChecked(False)                      # 取消全选
            except KeyError:
                pass

        def set_child_items_check_state(parent_item):
            for i in range(parent_item.childCount()):
                child_item=parent_item.child(i)
                child_item.setCheckState(column, parent_check_state)

                if child_item.checkState(column) == Qt.CheckState.Checked:  # 根据子项的新勾选状态处理列表
                    self.selected_module_values.add(int(child_item.text(1)))
                else:
                    try:
                        self.selected_module_values.remove(int(child_item.text(1)))
                    except KeyError:
                        pass
                set_child_items_check_state(child_item)  # 递归调用

2.3 导出模版改造

之前的导出:用例导出,sql查询用例,新建一个Excel同时写入。模板导出,会新建另外一个Excel模板,然后保存到文件夹。
  问题:有两段新建Excel的代码,冗余。导入,导出的Excel就不匹配,一致性不好。
  优化:定义一个新建模板的函数,用例导出:sql查询后,新建模板,打开模板写入。

2.3.1 导出按钮

  判断导出模块等条件后,将按钮失效,进行查询,如果查询到用例,就新建Excel,写入Excel。

    def download(self):
        """导出用例按钮"""
        if not self.selected_module_values:
            self.ts.xinxi("请选中模块")
        elif self.geshi.currentIndex() == 1:
            self.ts.xinxi("暂不支持格式")
        else:
            self.daochu.setEnabled(False)       # 导出按钮置灰
            items, items2=self.download_sql()   # 查询导出用例、模块
            self.daochulog.clear()              # 清空说明
            self.daochulog.append("导出数据说明:\n")
            if self.daochupath.text():
                path=self.daochupath.text()
            else:
                path=QStandardPaths.writableLocation(QStandardPaths.StandardLocation.DownloadLocation)
            self.daochulog.append(f"导出文件保存路径:{path}")
            self.daochulog.append(f"已选择模块:{len(self.selected_module_values)}个")

            if items:
                self.daochulog.append(f"查询到用例:{len(items)}条")
                current_time=datetime.now()
                formatted_time=current_time.strftime("%Y-%m-%d %H_%M_%S")
                excelname=f'{path}/导出用例{formatted_time}.xlsx'    # 保存路径 当前时间的时、分结尾命名
                self.muban_excel(excelname)      # 新建Excel
                self.down_excel(items, items2, excelname)    # 写入用例
            else:
                self.daochulog.append(f"选中模块用例数为【0】,请重新选择模块!")
            self.daochu.setEnabled(True)                           # 导出按钮恢复
            logging.info("导出用例按钮")

2.3.2 模板新建

  使用openpyxl,进行新建模板文件,共两个页面。

    def muban_excel(self, exclname):
        """新建模板Excel,传入Excel保存地址+名称:C:/Users/feng/Downloads/导出用例2024-02-04 09_36_31.xlsx"""
        workbook=Workbook()  # 新建
        sheet=workbook.active
        sheet.title="用例"

        # 创建单元格样式对象
        fill=PatternFill(start_color='F0F0F0', end_color='F0F0F0', fill_type='solid')  # 设置背景颜色(淡灰色)
        font=Font(bold=True)  # 设置字体加粗
        alignment=Alignment(horizontal='center', vertical='center')  # 设置水平和垂直居中对齐
        cell_style=NamedStyle(name="custom_style")  # 创建并定义样式
        cell_style.fill=fill
        cell_style.font=font
        cell_style.alignment=alignment
        workbook.add_named_style(cell_style)  # 将样式添加到工作簿的styles集合中
        # 创建红色字体样式
        red_font=Font(color="FF0000")  # 红色
        sheet.column_dimensions['B'].width=30  # 列宽
        sheet.column_dimensions['D'].width=30
        sheet.column_dimensions['E'].width=30
        sheet.column_dimensions['G'].width=30

        headers=["用例编号", "用例标题", "前置条件", "步骤", "预期", "关键词", "所属模块", "所属模块ID",
                 "优先级", "用例类型", "备注", "修改日期"]
        red_font_headers_indices=[headers.index('用例标题'), headers.index('步骤'), headers.index('所属模块'),
                                  headers.index('所属模块ID')]
        # 写入表头并设置特定列的字体颜色
        for col, header in enumerate(headers, start=1):  # 注意:openpyxl中的列索引从1开始
            cell=sheet.cell(row=1, column=col)  # 表头在第一行
            cell.value=header
            cell.style=cell_style
            if col - 1 in red_font_headers_indices:
                cell.font=red_font

        items=["1", "菜单验证", "", "打开页面跳转", "跳转正确", "菜单", "web功能测试用例", "12", "冒烟测试", "功能测试",
               "备注", "2024-01-17 15:46:50"]  # 示例数据
        priority_dropdown_values=['冒烟测试', '高', '中', '低']  # 设置优先级和用例类型的下拉列表
        case_type_dropdown_values=['功能测试', '性能测试', '配置相关', '安装部署', '接口测试', '安全相关', '兼容性测试',
                                   'UI测试', '其它']

        priority_col_index=headers.index('优先级') + 1  # 优先级列索引
        # 创建优先级的数据验证对象
        priority_data_validation=DataValidation(type="list", formula1='"{}"'.format(','.join(priority_dropdown_values)),
                                                allow_blank=True)
        sheet.add_data_validation(priority_data_validation)
        for row in range(2, 100):  # 从第二行开始应用(第一行为表头)
            priority_data_validation.add(sheet.cell(row=row, column=priority_col_index))

        case_type_col_index=headers.index('用例类型') + 1  # 同理为用例类型列设置下拉列表
        case_type_data_validation=DataValidation(type="list",
                                                 formula1='"{}"'.format(','.join(case_type_dropdown_values)),
                                                 allow_blank=True)
        sheet.add_data_validation(case_type_data_validation)
        for row in range(2, 100):
            case_type_data_validation.add(sheet.cell(row=row, column=case_type_col_index))

        for col, v in enumerate(items, start=1):  # 写入示例内容
            cell=sheet.cell(row=2, column=col)
            cell.value=v

        headers2=["模块编号", "父模块id", "层级", "节点路径", "名称"]
        items2=['1', '0', '0', '1',
                '不能导入模块,请手动新增模块']  # 示例数据
        module_sheet=workbook.create_sheet(title="模块")  # 模块
        module_sheet.column_dimensions['E'].width=45
        for col, header in enumerate(headers2, start=1):
            cell=module_sheet.cell(row=1, column=col)  # 表头在第一行
            cell.value=header
            cell.style=cell_style
        hang_number=2  # 初始化行数为2(从第二行开始写入)
        for col, value in enumerate(items2):  # 写入示例内容
            cell=module_sheet.cell(row=hang_number, column=col + 1)  # Excel索引是从1开始的,所以这里用col+1
            cell.value=value
        hang_number+=1  # 行数增加
        workbook.save(f'{exclname}')  # 保存

  对必填字段标红,优先级、用例类型设置下拉项。同时填充示例一条
在这里插入图片描述

2.3.3 导出用例写入

  打开新建的模板,分别写入用例、模块数据。

    def down_excel(self, items, items2, excelname):
        """打开Excel地址+名称excelname。写入用例items、模块items2。"""
        try:
            workbook=load_workbook(excelname)
            sheet=workbook["用例"]
            hang_number=2  # 初始化行数为2(从第二行开始写入)
            for item in items:
                for col, value in enumerate(item):
                    cell=sheet.cell(row=hang_number, column=col + 1)  # Excel索引是从1开始的,所以这里用col+1
                    cell.value=value
                hang_number+=1  # 行数增加

            module_sheet=workbook["模块"]
            hang_number2=2  # 初始化行数为2(从第二行开始写入)
            for item2 in items2:
                for col, value in enumerate(item2):
                    cell=module_sheet.cell(row=hang_number2, column=col + 1)  # Excel索引是从1开始的,所以这里用col+1
                    cell.value=value
                hang_number2+=1  # 行数增加

            workbook.save(f'{excelname}')  # 保存
            self.daochulog.append(
                f"\n-----------------用例导出完成,共导出【{hang_number - 2}】条用例-----------------")
        except Exception as e:
            self.daochulog.append(f"出错了:{e}")
            logging.error(e)

在这里插入图片描述

3、Excel导入用例

  导入流程:选择导入文件,点击导入。

3.1 导入按钮

  获取导入文件地址,导入按钮置灰,进行文件校验,校验通过读取数据,写入库表。完成后按钮恢复。

    def upload(self):
        """读取导入Excel"""
        self.daorulog.clear()               # 清空说明
        self.daorulog.append("导入数据说明:")
        if self.daoruwenjian.text():
            path=self.daoruwenjian.text()   # 导入文件地址
        else:
            self.daorulog.append("请选择导入文件")
            return
        self.daochu_2.setEnabled(False)     # 导入按钮置灰
        self.daorulog.append(f"导入文件路径:{path}")

        sheet=self.check_excl(path)         # 校验Excel格式
        if sheet:
            self.save_excl(sheet)           # 写入库表
        self.daochu_2.setEnabled(True)      # 导入按钮恢复
        logging.info("导入Excel")

3.2 校验Excel

打开Excel后,对文件进行校验。读取“用例”sheet页面,没有读取到报错。
  首先对行数、列数进行基本的验证。
  然后对4列必填项读取,与页面最大行数进行比对,验证是否必填。
  获取模块ID集合,查询出库内模块ID,进行对比。验证是否有效ID。
  对用例优先级、类型,进行码值验证。

    def check_excl(self,path):
        """校验Excel,输入文件地址。返回sheet"""
        try:
            wb=load_workbook(path)  # openpyxl,打开Excel
            sheet=wb["用例"]

            self.daorulog.append("\n文件校验中...")
            if sheet.max_row<=1:
                self.daorulog.append("\t校验失败:未读取到用例数据")          # 行列判断
                return
            elif sheet.max_column !=12:
                self.daorulog.append("\t校验失败:导入文件列数有误")
                return
            # 文件内容校验
            biaoti = [column.value for column in sheet['B'] if column.value is not None and column.value != ""]
            buzou= [column.value for column in sheet['D'] if column.value is not None and column.value != ""]
            mkname= [column.value for column in sheet['G'] if column.value is not None and column.value != ""]
            mkid= [column.value for column in sheet['H'] if column.value is not None and column.value != ""]
            required_columns=[biaoti, buzou, mkname, mkid]  # 长度,判断必填
            mkid_set=set(mkid[1::])         # 集合去重Excel数据,判断ID

            if not all(len(col) == sheet.max_row for col in required_columns):
                self.daorulog.append("\t校验失败:请检查必填项是否全部填写")
                return
            else:
                self.case_db.connect()
                moduleids=self.case_db.query_many("select moduleid from module where status = 10")  # 库内有效模块ID
                self.case_db.over()
                moduleids=set(id for id_tuple in moduleids for id in id_tuple)  #模块集合
                difference=mkid_set.difference(moduleids)       # 计算差集,判断模块ID
                if difference:
                    self.daorulog.append(f"\t校验失败:有未识别的模块ID{difference}")
                    return
            # 判断优先级、用例类型,码值
            yxj= [column.value for column in sheet['I'] if column.value is not None and column.value != ""]
            yllx= [column.value for column in sheet['J'] if column.value is not None and column.value != ""]
            yxj_set=set(yxj[1::])
            yllx_set=set(yllx[1::])
            if yxj_set:
                priority=tc_sql.codes_dict['priority'].keys()       # 优先级码值判断
                difference=yxj_set.difference(set(priority))  # 计算差集
                if difference:
                    self.daorulog.append(f"\t校验失败:有未识别的优先级{difference}")
                    return
            if yllx_set:
                tc_types=tc_sql.codes_dict['tc_types'].keys()       # 用例类型码值判断
                difference=yllx_set.difference(set(tc_types))  # 计算差集
                if difference:
                    self.daorulog.append(f"\t校验失败:有未识别的用例类型{difference}")
                    return
            self.daorulog.append("\t校验通过")
            return sheet
        except:
            self.daorulog.append(f"读取{path}失败,请检查文件与“用例”sheet\n")

3.3 写入库表

打开游标后,每次获取100行数据,读取每行进行数据转换,executemany一次插入多条语句。
  所有的Excel数据插入完成后,再进行事务提交,有问题进行回滚。保证结果一致性,同时避免重复提交,增加数据库时间开销。

    def save_excl(self,sheet):
        """读取导入Excel,写入到数据库"""
        self.daorulog.append("写入数据中...")
        row_start=2  # 起始行数
        num=0  # 计数
        current_time=datetime.now()
        formatted_time=current_time.strftime("%Y-%m-%d %H:%M:%S")
        self.case_db.connect()
        try:
            while row_start <= sheet.max_row:
                batch_data=[]  # 存储当前批次的用例数据
                batch_rows=min(sheet.max_row - row_start + 1, 100)  # 获取当前批次的行数(不超过剩余行数)
                # 遍历当前批次的行
                for row in sheet.iter_rows(min_row=row_start, max_row=row_start + batch_rows - 1):
                    data=[cell.value if cell.value is not None else '' for cell in row[1:11]]  # B-K列
                    data[5]=10  # 添加用例状态
                    data[8]=tc_sql.codes_dict['tc_types'].get(data[8], '') if data[8] else ''  # 码值转换
                    data[7]=tc_sql.codes_dict['priority'].get(data[7], '') if data[7] else ''
                    data.append(formatted_time)  # 修改时间
                    num+=1  # 计数
                    batch_data.append(tuple(data))

                if batch_data:
                    placeholders=','.join(['?'] * len(batch_data[0]))  # 将批次数据转换为可执行的SQL语句
                    insert_sql=f'INSERT INTO testcase (title,preconditions,step,expect,keyword,status,moduleid,priority,types,remarks,modificationdate) VALUES ({placeholders})'
                    self.case_db.cursor.executemany(insert_sql, batch_data)  # 批量插入数据
                    self.daorulog.append(f"\t已写入{num}条用例")
                row_start+=batch_rows  # 更新下一批的起始行
        except:
            self.case_db.conn.rollback()  # 发生错误时回滚事务
            self.daorulog.append("写入失败,数据已回滚")
        else:
            self.case_db.conn.commit()  # 在所有数据成功插入后提交事务
            self.daorulog.append(f"------------------导入完成,共写入:【{num}】条用例-----------------")
        finally:
            self.case_db.over()

3.4 导入实现情况

  现在导入文件只对格式与部分内容做了校验,没有对模块名称做校验。数据已经可以成功导入了。
在这里插入图片描述
代码放在Gitee

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

觅梦_feng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值