最全的接口自动化测试思路和实战:【推荐】混合测试自动化框架(关键字+数据驱动)

news2024/9/24 23:24:09

混合测试自动化框架(关键字+数据驱动)

关键字驱动或表驱动的测试框架

  这个框架需要开发数据表和关键字。这些数据表和关键字独立于执行它们的测试自动化工具,并可以用来“驱动"待测应用程序和数据的测试脚本代码,关键字驱动测试看上去与手工测试用例很类似。在一个关键字驱动测试中,把待测应用程序的功能和每个测试的执行步骤一起写到一个表中。

       这个测试框架可以通过很少的代码来产生大量的测试用例。同样的代码在用数据表来产生各个测试用例的同时被复用。

混合测试自动化框架

  最普遍的执行框架是上面介绍的所有技术的一个结合,取其长处,弥补其不足。这个混合测试框架是由大部分框架随着时间并经过若干项目演化而来的。

unittest关于测试报告展示用例名称的细节:

在unittest有两个内置属性,可以自定义用例名称

之前的效果:

 

 自动化测试相关教程推荐:

2023最新自动化测试自学教程新手小白26天入门最详细教程,目前已有300多人通过学习这套教程入职大厂!!_哔哩哔哩_bilibili

2023最新合集Python自动化测试开发框架【全栈/实战/教程】合集精华,学完年薪40W+_哔哩哔哩_bilibili

测试开发相关教程推荐

2023全网最牛,字节测试开发大佬现场教学,从零开始教你成为年薪百万的测试开发工程师_哔哩哔哩_bilibili

postman/jmeter/fiddler测试工具类教程推荐

讲的最详细JMeter接口测试/接口自动化测试项目实战合集教程,学jmeter接口测试一套教程就够了!!_哔哩哔哩_bilibili

2023自学fiddler抓包,请一定要看完【如何1天学会fiddler抓包】的全网最详细视频教程!!_哔哩哔哩_bilibili

2023全网封神,B站讲的最详细的Postman接口测试实战教学,小白都能学会_哔哩哔哩_bilibili

使用内置属性效果:

 

框架设计目标

  设计出来的框架是直接给测试人员,而且其他的测试人员只需要简单的向里面不断的补充测试用例即可;所以我们的框架设计必须三简化即操作简单,维护简单,扩展简单。

  设计框架的同时一定要结合业务流程,而不仅仅靠技术实现,其实技术实现不难,难点对业务流程的理解和把握。

  设计框架时要将基础的封装成公用的,如:get请求、post请求和断言封装成同基础通用类。

  测试用例要与代码分开,这样便于用例管理,采用数据驱动框架实现。

如下图所示:

  通过在excel录入测试用例,框架运行后自动进行用例执行,产生html网页版本的测试报告。

报告结果:

框架用到的技术点

1、语言:python

2、测试框架:unittest(assertEqual)或pytest

3、接口调用:requests(API非常简洁)

4、数据驱动:paramunittest (组装一定的格式数据就可以参数化)

5、数据管理:xlrd(读取excel文件数据)、configparser(读取配置文件)

6、数据格式的转换:ast,json

7、日志处理:logging ---清晰的执行过程,快速定位问题

8、测试报表:HTMLTestReportCN(由网友制作设计,显示清晰美观)

9、测试邮件发送测试报告:smtplib(邮件内容格式设置)、email(收发邮件)

10、持续集成:Jenkins(按策略执行接口测试脚本)

(推荐)混合测试自动化框架(关键字+数据驱动)

数据源实现:

数据源目前使用excel,数据如下:

链接:https://pan.baidu.com/s/1VvvGYRvGbElSlP6ngg0ktw
提取码:ppua

思路:使用python读取excel数据;使用xlrd3

框架01:新建项目API_KEY_WORD_TEST_FRAME;

步骤1、在项目根目录下新建common的py文件夹和conf的普通文件夹;samples文件夹是用来写测试代码的demo;

步骤2、在conf下新建config.ini文件

 编写代码:

[default]

# 主机地址
hosts = api.weixin.qq.com
步骤3、在common下新建ini_file_utils.py文件和config_utils.py文件

ini_file_utils.py文件如下:

 编写代码:

复制代码

# encoding: utf-8
# @author: Jeffrey
# @file: ini_file_utils.py
# @time: 2022/8/4 22:23
# @desc: 读取、写入ini文件
import os
import configparser


class IniFileUtils:  #和框架业务无关的底层代码==》公共底层代码

    def __init__(self,file_path):
        self.ini_file_path = file_path
        self.conf_obj = configparser.ConfigParser()
        self.conf_obj.read(self.ini_file_path, encoding='utf-8')

    def get_config_value(self,section, key):
        value = self.conf_obj.get(section, key)
        return value

    def set_config_value(self,section, key, value):
        '''设置config.ini文件中的值'''
        self.conf_obj.set(section, key, value)
        config_file_obj = open(self.ini_file_path, 'w')
        self.conf_obj.write(config_file_obj)
        config_file_obj.close()

if __name__ == '__main__':
    current_path = os.path.dirname(__file__)
    config_file_path = os.path.join(current_path, '../conf/config.ini')
    ini_file = IniFileUtils(config_file_path)
    print(ini_file.get_config_value('default', 'HOSTS'))

复制代码

config_utils.py文件如下:

 编写代码:

复制代码

# encoding: utf-8
# @author: Jeffrey
# @file: config_utils.py
# @time: 2022/8/4 22:26
# @desc: 封装读取ini文件的方法

import os
from common.ini_file_utils import IniFileUtils


current_path = os.path.dirname(os.path.abspath(__file__))
config_file_path = os.path.join(current_path, '../conf/config.ini')


class LocalConfig():  # #和框架业务有关系的底层代码

    def __init__(self,file_path = config_file_path):
        self.ini_file_obj = IniFileUtils(file_path)

    @property
    def get_hosts(self):
        '''获取ini文件中的hosts值'''
        hosts_value = self.ini_file_obj.get_config_value('default', 'hosts')
        return hosts_value


local_config = LocalConfig()

if __name__ == '__main__':
    print(local_config.get_hosts)

复制代码

步骤4、在samples文件下编写线性脚本,读取excel中的合并单元格

Excel表格如下:

 

 

 

 编写代码:

复制代码

# encoding: utf-8
# @author: Jeffrey
# @file: demo01.py
# @time: 2022/8/7 14:53
# @desc:  excel中合并单元格的读取
import xlrd3


work_book = xlrd3.open_workbook('test_data.xlsx')  # 创建一个工作簿对象
sheet_obj = work_book.sheet_by_name('Sheet1')  # 创建一个表格对象
print(sheet_obj.cell_value(1,2))  # 获取单元格的值(行,列),从0开始计算,行列实际序号-1
print(sheet_obj.cell_value(7,3))  # 获取单元格的值

print(sheet_obj.cell_value(1,0))  # 合并的单元格,获取的值为空
print(sheet_obj.cell_value(7,0))  # 合并的单元格,获取的值为空

# 包含四个元素(起始行,结束行,其实列,结束列) 包前不包后
print(sheet_obj.merged_cells)  # [(1, 5, 0, 1), (5, 9, 0, 1)]
# 思路步骤一:判断一个单元格是否是合并的单元格
x = 3 ; y = 0
if x>=1 and x<5:
    if y>=0 and y<1:
        print('合并单元格')
    else:
        print('非合并单元格')
else:
    print('非合并单元格')

# 思路步骤二:for循环的写法
for (min_row,max_row,min_col,max_col) in [(1, 5, 0, 1), (5, 9, 0, 1)]:
    print(min_row,max_row,min_col,max_col)

# 思路步骤三:把思路一和二整合进单元格判断是否是合并的单元格
x = 6 ; y = 0
for (min_row,max_row,min_col,max_col) in sheet_obj.merged_cells:
    if x >= min_row and x < max_row:
        if y >= min_col and y < max_col:
            print('合并单元格')
            break
        else:
            print('非合并单元格')
    else:
        print('非合并单元格')

# 思路步骤四:让合并单元格的值都等于合并第一个单元格的值,非合并单元格为原值
x = 4 ; y = 1
cell_value = None
for (min_row,max_row,min_col,max_col) in sheet_obj.merged_cells:
    if x >= min_row and x < max_row:
        if y >= min_col and y < max_col:
            cell_value = sheet_obj.cell_value(min_row,min_col)
            break
        else:
            cell_value = sheet_obj.cell_value(x, y)
    else:
        cell_value = sheet_obj.cell_value(x, y)
print(cell_value)

# 把上诉代码做成一个方法
def get_merged_cell_value(row_index,col_index):
    cell_value = None
    for (min_row, max_row, min_col, max_col) in sheet_obj.merged_cells:
        if row_index >= min_row and row_index < max_row:
            if col_index >= min_col and col_index < max_col:
                cell_value = sheet_obj.cell_value(min_row, min_col)
                break
            else:
                cell_value = sheet_obj.cell_value(row_index, col_index)
        else:
            cell_value = sheet_obj.cell_value(row_index, col_index)
    return cell_value

print(get_merged_cell_value(8,0))


# 获取excel中所有的数据 线性脚本
# 步骤一:线性脚本
head = sheet_obj.row_values(0)
print(head)  # ['学习课程', '步骤序号', '步骤操作', '完成情况']
excel_list = []
excel_dict = {}
excel_dict[head[0]] = get_merged_cell_value(1,0)
excel_dict[head[1]] = get_merged_cell_value(1,1)
excel_dict[head[2]] = get_merged_cell_value(1,2)
excel_dict[head[3]] = get_merged_cell_value(1,3)
excel_list.append(excel_dict)
print(excel_list)

# 步骤二: 使用for循环封装
head = sheet_obj.row_values(0)
excel_data_list = []
for j in range(1,sheet_obj.nrows):
    row_value_dict = {}
    for i in range(sheet_obj.ncols):
        row_value_dict[head[i]] = get_merged_cell_value(j,i)
    excel_data_list.append(row_value_dict)
print(excel_data_list)

for data in excel_data_list:
    print(data)

复制代码

步骤5、在common下新建excel_file_utils.py文件

 

 编写代码:

复制代码

# encoding: utf-8
# @author: Jeffrey
# @file: excel_file_utils.py
# @time: 2022/8/7 15:52
# @desc: 封装读取excel文件
import os
import xlrd3


class ExcelFileUtils():

    def __init__(self,excel_file_path, sheet_name):
        self.excel_file_path = excel_file_path
        self.sheet_name = sheet_name
        self.sheet_obj = self.get_sheet()

    def get_sheet(self):
        '''根据excel路径已经表名称 创建一个表格对象'''
        workbook = xlrd3.open_workbook(self.excel_file_path)
        sheet = workbook.sheet_by_name(self.sheet_name)
        return sheet

    def get_row_count(self):
        '''获取表格实际行数'''
        row_count = self.sheet_obj.nrows
        return row_count

    def get_col_count(self):
        '''获取表格的实际列数'''
        col_count = self.sheet_obj.ncols
        return col_count

    def get_merged_cell_value(self,row_index, col_index):
        '''
        :param row_index: 行下标
        :param col_index: 列下标
        :return: 获取单元格的内容
        '''
        cell_value = None
        for (min_row, max_row, min_col, max_col) in self.sheet_obj.merged_cells:
            if row_index >= min_row and row_index < max_row:
                if col_index >= min_col and col_index < max_col:
                    cell_value = self.sheet_obj.cell_value(min_row, min_col)
                    break
                else:
                    cell_value = self.sheet_obj.cell_value(row_index, col_index)
            else:
                cell_value = self.sheet_obj.cell_value(row_index, col_index)
        return cell_value

    def get_all_excel_data_list(self):
        '''获取excel中所有的数据,以列表嵌套字典的形式'''
        excel_data_list = []
        head = self.sheet_obj.row_values(0)
        for j in range(1,self.get_row_count()):
            row_value_dict = {}
            for i in range(self.get_col_count()):  # sheet_obj.ncols 动态获取表格多少列
                row_value_dict[head[i]] = self.get_merged_cell_value(j,i)
            excel_data_list.append(row_value_dict)
        return excel_data_list


if __name__ == '__main__':
    current_path = os.path.dirname(__file__)
    file_path = os.path.join(current_path, '../samples/test_data.xlsx')
    excel_obj = ExcelFileUtils(file_path, 'Sheet1')
    print(excel_obj.get_all_excel_data_list())
    print(excel_obj.get_col_count())

复制代码

测试执行结果:

步骤6、在项目根目录下新建test_data普通文件夹,把测试用例文件放里面

 

步骤7、在common下新建testcase_data_utils.py文件把测试用例的数据转换成框架需要用到的格式

 编写代码:

复制代码

# encoding: utf-8
# @author: Jeffrey
# @file: testcase_data_utils.py
# @time: 2022/8/7 17:02
# @desc: 数据格式转换:把测试用例数据转换成框架需要用到的格式
import os
from common.excel_file_utils import ExcelFileUtils

current_path = os.path.dirname(__file__)
file_path = os.path.join(current_path, '../test_data/testcase_infos.xlsx')


class TestCaseDataUtils:

    def __init__(self):
        self.test_data = ExcelFileUtils(excel_file_path=file_path,
                                        sheet_name='Sheet1')\
            .get_all_excel_data_list()

    def convert_testcase_data_to_dict(self):
        '''把excel中的测试用例数据转换为[{},{},{}]转换成{"":[],"":[],"":[]}'''
        test_case_data_dict = {}
        for row_data in self.test_data:
            test_case_data_dict.setdefault( row_data['测试用例编号'],[] )\
                .append(row_data)
        return test_case_data_dict

    def convert_testcase_data_to_list(self):
        '''把数据{"":[],"":[],"":[]} 转换成["case_id":key,"case_step":value]'''
        test_case_data_list = []
        for key, value in self.convert_testcase_data_to_dict().items():
            case_info_dict = {}
            case_info_dict['case_id'] = key
            case_info_dict['case_step'] = value
            test_case_data_list.append(case_info_dict)
        return test_case_data_list




if __name__ == '__main__':
    # print(TestCaseDataUtils().convert_testcase_data_to_dict())
    print(TestCaseDataUtils().convert_testcase_data_to_list())

复制代码

convert_testcase_data_to_dict()方法的目的是把用例数据以用例编号和用例数据分开

'''把excel中的测试用例数据转换为[{},{},{}]转换成{"":[],"":[],"":[]}'''

查看执行结果:

 总结:

 光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

如果对你有帮助的话,点个赞收个藏,给作者一个鼓励。也方便你下次能够快速查找。

如有不懂还要咨询下方小卡片,博主也希望和志同道合的测试人员一起学习进步

在适当的年龄,选择适当的岗位,尽量去发挥好自己的优势。

我的自动化测试开发之路,一路走来都离不每个阶段的计划,因为自己喜欢规划和总结,

测试开发视频教程、学习笔记领取传送门!!

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1227975.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

asp.net mvc点餐系统餐厅管理系统

1. 主要功能 ① 管理员、收银员、厨师的登录 ② 管理员查看、添加、删除菜品类型 ③ 管理员查看、添加、删除菜品&#xff0c;对菜品信息进行简介和封面的修改 ④ 收银员浏览、搜索菜品&#xff0c;加入购物车后进行结算&#xff0c;生成订单 ⑤ 厨师查看待完成菜品信息…

4 redis的HyperLogLog入门原理

一、HyperLogLog&#xff08;字符串类型&#xff09; 需求&#xff1a;大型网站(不在大厂基本上用不到) 每个网页每天的 UV 数据(独立访客)&#xff0c;统计如何实现&#xff1f;(尽量少的占用存储空间) Redis 提供了 HyperLogLog 数据结构就是用来解决这种统计问题的。Hyper…

Spring cloud - Hystrix服务限流、熔断及降级

Hystrix的作用 Hystrix的主要作用是在微服务环境下防止服务雪崩&#xff0c;确保服务弹性及可用性。 具体来说&#xff0c;Hystrix可以实现&#xff1a; 服务降级&#xff1a;通过fallback实现服务不可达情况下的服务降级作用。熔断&#xff1a;服务不可达的情况下在设定时间…

股东入股可用的出资形式主要有哪些

股东入股&#xff0c;可用的出资形式主要包括货币以及实物、知识产权、土地使用权等可以用货币估价并可以依法转让的非货币财产。 第一&#xff0c;货币。设立公司必然需要一定数量的流动资金。以支付创建公司时的开支和启动公司运营。因此&#xff0c;股东可以用货币出资。 第…

【LeetCode刷题-树】--1367.二叉树中的链表

1367.二叉树中的链表 方法&#xff1a;枚举 枚举二叉树中的每个节点为起点往下的路径是否与链表相匹配的路径&#xff0c;为了判断是否匹配设计了一个递归函数dfs(root,head),其中root表示当前匹配到的二叉树节点&#xff0c;head表示当前匹配到的链表节点&#xff0c;整个函数…

实战项目:VB龟兔赛跑游戏+猜数字游戏

文章目录&#xff1a; 一&#xff1a;效果演示 二&#xff1a;实现思路 三&#xff1a;代码实现 form1 效果图 代码 form2 效果图 代码 form3 效果图 代码 一&#xff1a;效果演示 效果图◕‿◕✌✌✌ 代码下载 二&#xff1a;实现思路 窗口1&#xff1a;龟兔赛…

2023上海初中生古诗文大会复赛12月2日举行,关键事项为您划重点

今天中午12点&#xff0c;古诗文大会官微发布消息&#xff1a;2023上海中学生古诗文大会&#xff08;初中组&#xff09;复选将于12月2日举行。 具体安排和注意事项、常见问题&#xff0c;六分成长为您整理如下。 一、2023年初中生古诗文大会复赛日期和时间 12月2日&#xff…

【Java 进阶篇】JQuery 事件绑定之事件切换:让页面动起来

欢迎来到这个充满动感的 JQuery 事件绑定之旅&#xff01;在这篇博客中&#xff0c;我们将深入研究 JQuery 中的事件切换&#xff0c;让你的页面焕发出活力和互动。无论你是前端小白还是有一定经验的开发者&#xff0c;相信这篇文章都会对你有所帮助。 走进事件切换的奇妙世界…

redis三种集群方式

redis有三种集群方式&#xff1a;主从复制&#xff0c;哨兵模式和集群。 1.主从复制 主从复制原理&#xff1a; 从服务器连接主服务器&#xff0c;发送SYNC命令&#xff1b; 主服务器接收到SYNC命名后&#xff0c;开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所…

学习网络编程No.10【深入学习HTTPS】

引言&#xff1a; 北京时间&#xff1a;2023/11/14/18:45&#xff0c;因为种种原因&#xff0c;上个月的文章昨天才更新&#xff0c;目前处于刷题前夕&#xff0c;算法课在看了。这次和以前不一样&#xff0c;因为以前对知识框架没有很好的理念&#xff0c;并不清楚相关知识要…

HWS-CTF-第七期山大站-inverse

文章目录 inversemainworkread_intread_n 思路onegadget exp 第一次真正意义上独立在比赛中做出题目来了&#xff0c;距离真正意义接触CTF-PWN差不多正好两个月。但由于不知道靶场要自己开而且端口每次自己打开会改&#xff0c;交flag稍微晚了些&#xff08;我太菜了&#xff0…

4种经典的限流算法与集群限流

0、基础知识 1000毫秒内&#xff0c;允许2个请求&#xff0c;其他请求全部拒绝。 不拒绝就可能往db打请求&#xff0c;把db干爆~ interval 1000 rate 2&#xff1b; 一、固定窗口限流 固定窗口限流算法&#xff08;Fixed Window Rate Limiting Algorithm&#xff09;是…

如何从Android恢复出厂设置后的手机恢复数据

如果您已使用出厂设置删除了Android设备上的所有数据&#xff0c;或者有一段时间未使用&#xff0c;则需要恢复出厂设置以从Android设备中检索数据。 奇客数据恢复安卓版是一个有用的工具&#xff0c;可以在重置后检索Android数据。 将Android设备恢复出厂设置 如果您需要将A…

简单聊一聊幂等和防重

大家好&#xff0c;我是G探险者。 每年的双十一&#xff0c;618&#xff0c;电商系统都会面临这超高的流量&#xff0c;如果一个订单被反复提交&#xff0c;那电商系统如何保证这个订单之后执行一次减库存&#xff0c;扣款的操作&#xff1f; 这里就引入两个概念&#xff0c;…

integrin + Receptor ; platelet ; Ig-CAM

platelet HPA ; integrin ; Cell adhesion in cancer: Beyond the migration of single cells

SQL零基础入门教程,贼拉详细!贼拉简单! 速通数据库期末考!(十)

SQL 函数 SQL 拥有很多可用于计数和计算的内建函数。 比如&#xff1a; AVG() - 返回平均值 COUNT() - 返回行数 MAX() - 返回最大值 MIN() - 返回最小值 SUM() - 返回总和 FIRST() - 返回第一个记录的值 LAST() - 返回最后一个记录的值 GROUP BY 学习SQL函数前&#xff0c…

用GPT 搭建一个占星术、解梦、塔罗牌占卜和命理学服务

今天来尝试我们的占星术、解梦、塔罗牌占卜和命理学服务&#xff0c;揭开宇宙的奥秘并获得自我认识 聊天 GPT API 集成的 HTML5 模板。我们的目标是提供易于使用且高度可定制的 API 代码&#xff0c;使您能够训练自己的人工智能解决方案并将其添加到提示中。 我们的产品是可定…

JSP命令标签 静态包含/动态包含

好 下面我们聊聊JSP中的指令标签 这边 我们来说两个 分别是 静态包含 和 动态包含 我们可以将重用性代码包含起来 更好的使用 比如 我们界面上中下 分别有三个导航栏 那么 如果你写三份 就会出现很多重复代码 而且 改起来 也很不方便 要一次改三份 口说无凭 我们来做一个小案…

【Linux】第十九站:进程替换

文章目录 一、单进程版---最简单的程序替换二、进程替换的原理三、多进程的程序替换1.多进程的程序替换实例2.那么程序在替换时候有没有创建子进程呢3.再谈原理4.一个现象5.我们的CPU如何得知程序的入口地址&#xff1f; 四、各个接口的介绍1.execl2.execlp3.execv4.execvp5.ex…

【机器学习13】生成对抗网络

1 GANs的基本思想和训练过程 生成器用于合成“假”样本&#xff0c; 判别器用于判断输入的样本是真实的还是合成的。 生成器从先验分布中采得随机信号&#xff0c;经过神经网络的变换&#xff0c; 得到模拟样本&#xff1b; 判别器既接收来自生成器的模拟样本&#xff0c; 也接…