• python+requests实现接口自动化


    1. 前言

    今年2月调去支持项目接口测试,测试过程中使用过postman、jmeter工具,基本能满足使用,但是部分情况下使用较为麻烦。
    比如:部分字段存在唯一性校验或字段间有业务性校验,每次请求均需手工修改部分报文内容,使用工具难以满足实际使用。
    因此,萌生了使用python去实现接口自动化的想法。之前未接触过接口测试,但有一点编程基础,经过2个多月的磕磕碰碰,不断完善,经历2次重构之后,最后基本达成了目标。

    2. 技术栈

    • python语言
    • requests库
    • unittest单元测试框架
    • HTMLTestReportCN、BeautifulReport测试报告

    3. 实现的功能概述

    • 支持post、get等请求类型,xml、json格式的报文
    • 支持使用excel编写测试用例,测试用例支持涉及多接口的场景用例;支持按脚本的形式编写测试用例
    • 支持测试结果保存至数据库,支持生成html报告,支持将生成的测试结果导出到excel文件
    • 支持邮件发送测试结果
    • 支持多线程并发执行测试用例

    4. 框架及项目结构

    APIS_AutoTest

    api: 主程序目录

    comm:公共函数,包括:接口请求基类、请求及相应数据操作基类等

    intf_handle:接口操作层,包含:接口初始化、断言等

    business:业务实现部分

    utils:工具类,包括:读取文件、发送邮件、excel操作、数据库操作、日期时间格式化等

    config:配置文件目录,包含yaml配置文件、以及路径配置

    data:测试数据目录,用于存放测试数据

    temp:临时文件目录,用于存放临时文件

    result:结果目录

    report:测试报告目录,用于存放生成的html报告

    details:测试结果详情目录,用于存放生成的测试用例执行结果excel文件

    log:日志文件目录

    test:测试用例、测试集相关目录,启动test_suite执行用例文件存放在此

    test_case:测试用例存放路径

    test_suite:测试模块集,按模块组装用例

    5. 测试用例执行流程

    • 以脚本形式编写的测试用例执行流程图:

       
    • 以excel形式编写的测试用例执行流程图:

       

    6. 核心方法设计

    • 接口请求基类

      RequestBase(request_type, url, header, body, data_type=None)

    功能描述:

    根据传入的请求类型,请求地址,请求头,请求体,发送接口请求,获得响应头,响应体

    • request_type: 请求类型,只能是'GET', 'POST', 'HEAD', 'OPTIONS', 'PUT', 'DELETE', 'TRACE', 'CONNECT'中的一个,无大小写要求
    • url: 请求地址,完整的接口地址
    • head: 请求头
    • body:请求体,xml格式请求体为字符串;json格式请求体需传入json格式
    • data_type: 数据类型,未使用字段,备用。可为:xml、json

    支持方法

    __send_request()
    发送请求,类私有方法,初始化时调用,调用方法返回:响应对象

    get_respond()
    获取响应对象,调用方法返回:响应对象

    get_respond_head()
    获取响应头,调用方法返回:响应头

    get_respond_body()
    获取响应体,调用方法返回:响应体


    • 请求或响应数据操作基类

      RequestRespondHandle(data_type, body, fields, value_dict=None)

    功能描述:

    根据传入的xml、json格式请求体或响应体,读取字段的值或更新字段的值

    参数描述:

    • data_type: 数据类型,当前仅支持xml、json,不区分大小写
    • body: 请求体或响应体,json格式请求体支持传入字典、json格式数据(自动转换为字典)
    • fields: 字段名称,支持多字段传入,支持数据类型:字符串、元组、列表。多字段传入形式,字符串:多个字段名称之间逗号隔开,如:'a1,b2';元组、列表正常传入即可。json格式:需写入完整节点路径,如:body.base.name,对应list类型的需传入索引位置,如:body.baselist[0].name
    • value_dict: 字段值字典,以字段名称及字段值键值对的方式存储数据,读取字段值时,一般不需传入,也支持传入(用于读取excel形式流程前后传值);更新字段值时,需传入,且字典中的key值需要与fields中的字段名对应。

    支持方法:

    get_fields_value()
    获取字段值,调用方法即可获取到xml或json格式的请求体或响应体对应的字段值,并返回:字段值字典

    update_fields_value()
    更新字段值,调用方法即可更新请求体中对应的字段值,返回更新后的请求体。
    Json格式请求体返回格式为字典。

    不足与改进:

    xml格式,读取或更新字段时,若存在多个相同名称字段,默认只选第一个;
    json格式,读取嵌套列表的时候,未支持按列表读取,当前需精确位置单个读取或更新;


    • 请求体初始化-接口映射类

      RequestMsgInitMapper(data_type, intf_code, request_body, **kwargs)

    功能描述:

    根据传入的接口编号,映射到对应的接口请求报文的初始化方法,进行接口初始化

    参数描述:

    • data_type: 数据类型,执行xml、json,不区分大小写
    • intf_code: 接口编号,每个接口都要一个接口编号,根据接口编号可以唯一确定一个接口
    • request_body: 请求体,json格式请求体支持传入字典、json格式数据
    • **kwargs: 可变关键字参数,每个接口初始化时,参数数量均不一致,所有需要使用可变参数,需要以a=value这种方式传入。部分字段(如id)可在方法内设置生成方法,一般可变参数设置都是用于前后接口字段传值。

    支持方法:

    start_Intf_init_mapper()
    启动 接口初始化映射,该方法通过判断传入数据类型、接口编号,执行对应的接口初始化方法。
    调用方法后,进行对应接口的初始化,并返回初始化后的请求体。
    Json格式的数据,返回json格式的请求体(不管传入的是字典格式、还是json格式)。

    适用场景说明:

    接口映射类主要是针对不同接口初始化字段涉及复杂业务判断,需按接口单独编写的场景。
    如果一个接口初始化涉及的字段均不涉及业务相关的复杂判断处理,可以直接统一使用通用接口初始化方法进行初始化。


    通用接口初始化方法

    intf_base_init(data_type, request_body, **kwargs)

    功能描述:

    根据可变关键字参数传入的键值对,进行接口报文初始化,返回初始化后的请求体

    参数描述:

    • data_type: 数据类型,执行xml、json,不区分大小写
    • request_body: 请求体,json格式请求体支持传入字典、json格式数据
    • **kwargs:可变关键字参数,每个接口初始化时,参数数量均不一致,所有需要进行初始化的字段,均需以键值对的方式传入。

    适用场景说明:

    接口初始化字段不涉及复杂逻辑判断,直接传值后更新即可;该方式也适用于接口请求头的初始化。

    7. 数据操作类

    数据操作主要是数据读取、写入,主要分为如下几类:

    - 读取txt、json等文件内容(整个读取)、写入内容到文件
    - 读取yaml文件内容
    - 读取excel表格内容、导出excel表格、读取excel并作为模板
    - 操作数据库表中的数据(增删改查)

    读取txt/json文件内容,写入内容到文件

    FileHandle(file_name, file_path)

    功能描述:

    读取文件所有内容

    参数描述:

    • file_name:读取/写入文件名称,包含后缀,支持txt,json等
    • file_path:读取/写入文件所在的目录

    支持方法:

    read_file_content()
    读取文件内容,并以字符串返回

    write_to_file(content)
    将内容写入文件


    读取yaml文件内容

    ReadYaml(file_path)

    功能描述:

    读取yaml文件内,支持按名称读取

    参数描述:

    • file_path:读取文件的完整路径

    支持方法:

    get_yaml()
    读取yaml文件所有内容,并以字典格式返回

    get_value(level_name)
    读取yaml文件字段的值,并返回
    level_name: 节点字段名称,如涉及多节点需传入对应路径,如:db.host


    读取excel表格内容、导出excel表格、读取excel并作为模板

    ExcelHandle()

    功能描述:

    读取excel表格内容、将数据导出到excle表格中

    支持方法:

    read_excel_data(excel_path, sheet_name=None)

    读取excel表格内容,并以列表嵌套列表的方式返回

    excel_path: 读取excel文件的完整路径
    sheet_name: 读取excel的页签名称,默认读取第一个页签

    export_to_excel(data, head, file_name, time_flag=None)

    将数据导出到excle表格中

    data: 需要导出到excel的内容,元组嵌套元组(对应数据库中查询返回的结果)
    head: excel表头,列表、元组嵌套元组(数据库中查询表头、描述,支持多表头)
    file_name: excel文件名称,包含后缀.xlsx,excel导出路径系统默认
    time_flag: 时间戳标记,可传入用例执行的报告号,使其对应

    copy_excel_template(template_path, sheet_name=None)

    复制excel表格模板,返回workbook, workssheet

    template_path: 模板文件完整路径
    sheet_name: 模板文件页签名称,默认第一个页签


    操作数据库数据

    功能描述:

    • 表数据读取:根据情况读取所需数据,返回数据格式元组嵌套元组。
    • 插入/更新表数据:根据实际内容,写入数据到对应的表中

    备注:

    数据库部分封装为数据库操作类

    8. 部分关键代码

    接口请求基类

    # create by: wyun
    # create at: 2020/4/19 9:30
    import json

    import requests


    class RequestBase:

    def __init__(self, request_type, request_url, request_body, request_headers, intf_type=None):
    """
    请求接口公共类
    :param request_type: 请求类型:post, get
    :param request_url: 请求url地址
    :param request_headers: 请求头
    :param request_body: 请求体(xml类型传入字符串格式,json类型数据必须传入json格式,不能传入字典)
    :param intf_type: 接口类型:webservice, api,分别对应接口数据格式:xml, json
    """
    self.request_type = request_type
    self.request_url = request_url
    self.request_body = request_body
    self.request_headers = request_headers

    self.intf_type = intf_type
    self.res = self.__send_request()

    # 发送请求
    def __send_request(self):
    if not isinstance(self.request_type, str):
    print('请求类型格式错误。')
    return None
    if self.request_type.upper() not in ['GET', 'POST', 'HEAD', 'OPTIONS', 'PUT', 'DELETE', 'TRACE', 'CONNECT']:
    print('请求类型不存在。')
    return None
    return requests.request(method=self.request_type, url=self.request_url, data=self.request_body.encode('utf-8'),
    headers=self.request_headers)

    # 获取响应
    def get_respond(self):
    return self.res

    # 获取响应头
    def get_respond_head(self):
    return self.res.headers

    # 获取响应体
    def get_respond_body(self):
    if self.res.content:
    return self.res.text

    请求或响应数据操作基类

    # create by: wyun
    # create at: 2020/4/19 12:58
    import json
    import re

    """
    对请求体或响应体进行处理:
    1. 支持读取请求体或响应体中字段的值
    2. 支持更新请求体中字段的值
    """


    # 递归调用,更新json中的字段值
    def update_json_step(node, json_str, value, i=0):
    # 当前节点索引(负向)
    node_index = -len(node) + i

    # 如果包含[n]形式,说明节点为列表,需处理
    if '[' in node[node_index] and ']' in node[node_index]:
    list_node, list_index = node[node_index].split('[')
    index = list_index.split(']')[0]
    if index is None or index == '':
    print('参数传入错误,请指定列表[%s]索引' % list_node)
    else:
    index = int(index)
    return update_json_step(node, json_str[list_node][index], value, i + 1)

    # 判断如果当前节点为最后一个节点,则更新value
    if node_index == -1:
    json_str[node[-1]] = value
    return json_str

    return update_json_step(node, json_str[node[node_index]], value, i + 1)


    class DataHandle:

    def __init__(self, data_type, data_msg, fields, value_dict=None):
    """
    处理请求体或响应体数据,读取或更新字段值
    :param data_type: 数据类型:xml,json
    :param data_msg: 请求体或响应体
    :param fields: 字段值,支持多字读方式,字段间逗号隔开;或传入列表、元组
    json格式:需写入完整节点路径,如:body.base.name,对应list类型的需传入索引位置,如:body.baselist[0].name
    :param value_dict: 以字典键值对保存字段值
    """
    self.data_type = data_type
    self.data = data_msg
    # fields支持strlist方式,str自动转换为list
    if isinstance(fields, str):
    self.fields = fields
    self.fields_list = []
    if ',' in fields:
    self.fields_list = fields.strip().split(',')
    else:
    self.fields_list.append(fields.strip())
    elif isinstance(fields, list):
    self.fields_list = fields
    else:
    self.fields_list = list(fields)
    # 初始化字典值
    if value_dict is None:
    self.value_dict = dict()
    else:
    self.value_dict = value_dict

    def get_fields_value(self):
    """
    获取字段值
    1. 支持获取多个字段值,输入字符串或列表, 元组
    """
    step_dict = self.value_dict
    # 字段列表循环获取
    for p in self.fields_list:
    if p == '':
    continue
    # 处理xml格式报文
    if self.data_type.upper() == 'XML':
    pattern = '<' + p + '>.*</' + p + '>'
    search_result = re.search(pattern, self.data)
    if search_result is not None:
    # 包括标签和值都匹配上
    field_and_value = search_result.group()
    # 去除标签获取字段值,并存入字典
    step_dict[p] = (field_and_value.split('</')[0]).split('>')[-1]
    else:
    # print('参数[%s]提取失败,无匹配值。' % p)
    pass
    # 处理json格式报文
    elif self.data_type.upper() == 'JSON':
    # 获取层级(使用.隔开)
    node = p.split('.')
    # json转换字典
    if isinstance(self.data, dict):
    temp = self.data
    else:
    temp = json.loads(self.data)

    # 逐层读取数据
    for per_node in node:
    if '[' in per_node and ']' in per_node:
    per_node, index_str = per_node.split('[')
    index = int(index_str.split(']')[0])
    temp = temp.get(per_node)[index]
    if temp is None:
    break
    else:
    temp = temp.get(per_node)
    if temp is None:
    break
    # 讲读取的结果写入字典
    step_dict[p] = temp
    # xmljson的数据格式,报错退出
    else:
    print('ERROR: 不支持此数据类型[%s]' % self.data_type)
    break
    return step_dict

    def update_fields_value(self):
    """ 更新字段值 支持更新单个字段值,多个字段值(字段名 字符串或列表、元组,字段值 字典) """



    req_body = self.data
    # 字段列表循环更新
    for p in self.fields_list:
    # 入参类型判断
    if isinstance(self.value_dict, dict):
    # 参数在字典中不存在,则跳过
    if p not in self.value_dict.keys():
    print('ERROR字典中不存在参数[%s]。' % p)
    continue
    # 获取字典中参数的值
    value = self.value_dict[p]
    elif isinstance(self.value_dict, str):
    value = self.value_dict
    else:
    print('函数入参[%s]类型不支持,请传入字符串或字典。' % self.value_dict)
    break
    # 更新数据类型为 xml 的参数值
    if self.data_type.upper() == 'XML':
    # 优先寻找是否存在指定更新参数,若有,则按指定参数更新
    if '${' + p + '}' in req_body:
    req_body = req_body.replace('${' + p + '}', value)
    # 其次寻找标签参数值,若有,则按标签更新值
    else:
    # 检查标签是否存在,若存在
    if '<' + p + '>' in req_body and '</' + p + '>' in req_body:
    # 正则匹配
    pattern = '<' + p + '>.*</' + p + '>'
    new_field_and_value = '<' + p + '>' + str(value) + '</' + p + '>'
    search_result = re.search(pattern, req_body)
    # 若正则匹配结果存在,则进行字段值更新
    if search_result is not None:
    old_field_and_value = search_result.group()
    req_body = req_body.replace(old_field_and_value, new_field_and_value)
    else:
    print('参数[%s]匹配标签失败,请检查报文中标签格式。' % p)
    else:
    # print('参数[%s]匹配标签失败,无此标签。' % p)
    continue
    # 更新数据类型为 xml 的参数值
    elif self.data_type.upper() == 'JSON':
    # json转换字典
    if isinstance(self.data, dict):
    req_body = self.data
    else:
    req_body = json.loads(self.data)
    # 获取层级
    node = p.split('.')
    # 调用递归函数更新字段值
    update_json_step(node, req_body, value)

    # xmljson的数据格式,报错退出
    else:
    print('ERROR: 不支持此数据类型[%s]' % self.data_type)
    break

    return req_body

    接口初始化通用类

    # create by: wyun
    # create at: 2020/4/23 22:42
    from api.comm.data_handle import DataHandle


    def intf_base_init(data_type, request_body, **kwargs):
    """
    接口初始化通用类
    :param data_type: 数据类型,如:xml,json
    :param request_body: 请求体
    :param kwargs: 可变关键字参数
    :return: 请求体
    """
    return DataHandle(data_type, request_body, kwargs.keys(), kwargs).update_fields_value()



    如果你处于想学Python自动化或者正在学习Python自动化,Python自动化的教程不少了吧,但是是最新的吗?
    说不定你学了可能是一年前人家就学过的内容,干货分享一波,2020最新的Python教程。



  • 相关阅读:
    [转载]DataView详解
    (转)C#中“EQUALS”与“==”的速度比较
    CS0016: 未能写入输出文件“c:WindowsMicrosoft.NETFramework64v2.0.50727Temporary ASP.NET Files oot921bbfc4ca7cf42App_Code.fu98jwep.dll”--“拒绝访问。 ”
    C# 配置错误定义了重复的“system.web.extensions/scripting/scriptResourceHandler”节
    C# 利用mysql.data 在mysql中创建数据库及数据表
    【转载】经典SQL语句大全
    我不是一个做产品人,但我有一颗做产品的心--浅谈“痛点”
    软件工程--个人总结
    第十六周进度条
    梦断代码阅读笔记3
  • 原文地址:https://www.cnblogs.com/cemaxueyuan/p/12830399.html
Copyright © 2020-2023  润新知