python-自动化测试- 多接口动态参数设计

什么叫做多接口的动态参数处理?

测试数据都放在excel中管理,每个接口如果有提取的数据就直接放在excel里写好,到时候直接读取出来做响应的提取操作即可。

  • 电商项目: 购物车 token proId skuID等 需要执行多个接口 获取返回值;

  • 业务流: 前面过很多步骤 【中间步骤都是单独的一个接口的请求】

  • 以上情况 都不太适合用conftest夹具测试使用。因为前置很多 处理比较麻烦。

  • 参考postman的处理方法。

    • 先执行前置登录-- 正常接口测试
    • 执行之后,提取数据-- 存在环境变量— 共享的 后面每个接口都可以调用数据,变量
    • 后面接口调用 – {{变量}} === 这个是{{}}是一个占位符,用你从环境变量里取到值 替换掉这个位置的数据 【Jmeter - ${}】
  • 设计的思路:业务流: 登录-搜索-进入详情页-添加购物车-查询购物车-结算-提交订单

    • 先把业务流的接口用例都写出来,在excel表格里统一管理表格设计好之后,我们需要用代码来实现: 提取数据---->存储环境变量—> 查找占位符 —> 替换变量的一系列操作。我们一步一步完成。
      • 1、关联的接口 需要提取的数据也提取出来: 加一个提取响应字段在excel里,用jsonpath提取
      • 2、代码提取出来后,保存在环境变量里 == 类 动态属性
      • 3、其他接口要用的位置用占位符表示,后面用代码替换成为 -环境变量里存的值。
  • 第一步:提取出来需要的响应字段的值 - 用jsonpath
    思路如下:

    • 1、先从excel标题读取【提取响应结果数据】这列; – 读取出来是字符串
    • 2、转化为字典-- json.loads()反序列化
    • 3、分别渠道这个提取表达式里的 key 和value ,key是字段名,value是jsonpath表达式。 – 用for循环遍历
    • 4、从接口的响应结果里使用这个jsonpath表达式提取数据。
  • 第二步:将响应字段存储到环境变量中(键-值,可以有多组): 数据读取出来后,要存起来: 全局共享给所有用例可用。

    • 用类的动态属性存储提取是的数据。
    • 类和类的属性 存出 环境变量的变量名 和值。 == setattr getattr
  • 第四步:用占位符包裹要替换的变量,然后用环境变量里的数据替换占位符位置的变量名。

    • 难点:代码如何自动识别这些要替换的数据?–用占位符 ; 占位符包裹的变量名出来? — 正则表达式匹配
      如果能识别出来,我们后续其实就比较简单了:
      • 1、取出来占位符中的变量名: 读取头部和参数的时候,判断是否有替换的值。有就替换。
      • 2、从环境变量获取属性值-- 变量值
      • 3、将变量名替换为属性值,再去用于发送接口请求【头部也有,参数也有】
"""
思路如下:
1、先从excel标题读取【提取响应结果数据】这列; -- 读取出来是字符串
2、转化为字典-- json.loads()反序列化
3、分别渠道这个提取表达式里的 key 和value ,key是字段名,value是jsonpath表达式。 -- 用for循环遍历
4、从接口的响应结果里使用这个jsonpath表达式提取数据。
"""
# 从excel里读取出来的 提取形影结果的表达式
import json
from jsonpath import jsonpath

# 这个类就是为了存储环境变量 实现环境变量的共享的
class EnviData:
    pass


response = {"access_token":"0efdce50-0e2f-4ed0-b4d1-944be5ab518a",
"token_type":"bearer","refresh_token":"4bfc3638-e7e4-4844-a83d-c0f8340bc146",
"expires_in":1295999,"pic":"http://mall.lemonban.com:8108/2023/09/b5a479b28d514aa59dfa55422b23a6f0.jpg",
"userId":"46189bfd628e4a738f639017f1d9225d","nickName":"lemon_auto","enabled":True}

extract_data = '{"access_token":"$..access_token","token_type":"$..token_type"}'

# 第一步: 反序列化 -字典
extract_data = json.loads(extract_data)
for k,v in extract_data.items():  # k 是access_token 变量名字,v是$..access_token
    # 使用jsonpath表达式 提取login响应结果里的值
    value = jsonpath(response,v)[0]  # 是access_token的具体值
    print(value)
    # 存起来到环境变量里去
    setattr(EnviData,k,value)
print(EnviData.__dict__)

运行结果如下:

0efdce50-0e2f-4ed0-b4d1-944be5ab518a
bearer
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'EnviData' objects>, '__weakref__': <attribute '__weakref__' of 'EnviData' objects>, '__doc__': None, 'access_token': '0efdce50-0e2f-4ed0-b4d1-944be5ab518a', 'token_type': 'bearer'}

正则表达式提取

  • 正则表达式是一组由字母和符号组成的特殊文本, 它可以用来从文本中找出满足你想要的格式的句子.
  • 主要针对字符串:
    • 1、判断字符串是否匹配我的规则
    • 2、提供正则表达式,从字符串提取出来匹配的内容。
  • 正则非常强大,开发特别是前端开发用的很多,语法非常多,没有必要都掌握,用到了再去查就可以。目前先学习我们要用的即可。

re相关的文章: https://www.cnblogs.com/Simple-Small/p/9150947.html

regular表达式学习手册: https://tool.oschina.net/uploads/apidocs/jquery/regexp.html(规则)

https://gitee.com/thinkyoung/learn_regex

在线的正则验证:https://c.runoob.com/front-end/854/

  • 正则的语法规则:

    • 1、匹配一个字符 (元字符)
      • \d 只匹配数字0-9
      • \w 匹配[0-9A-Za-z_] 数字字母下划线 支持中文
      • . 除了换行符(\n)以外的所有字符。
    • 2、正则的匹配次数
      • *匹配前一个字符,0次或者多次 ==任意次
      • +匹配前一个字符,1次或者多次
      • ?匹配前一个字符,0次或者1次
  • 我们用的做多就是万能表达式: 非贪婪匹配的 .*?

    • ? 非贪婪匹配模式:找到了## 两个后 不会再找了就匹配成功了;
      • 贪婪模式:尽可能去进行更多的匹配
      • 非贪婪模式:尽可能少的去进行匹配
  • Python的正则的库,内置库 re , 直接导入re 库,免安装

    • re模块函数 : findall(),search()
      • findall(正则表达式, 目标字符串) ,字符串从头到尾去匹配,只要符合要求就会拿出来
        • 多个符合的结果 存到列表里,返回一个列表。
        • 如果只想获取里面的变量名 不需要这个## 限定符,可以加个括号括起来: 加了括号,就是只提取括号里的内容,就是左右边界了。
      • search(正则表达式, 目标字符串)方法
        • 搜索查找,如果匹配的正则符合的话,返回Match类型的对象,不符合就返回None
        • Match类型的对象(search函数的返回值)的方法group() - 正则分组,可以返回的匹配的整体子串-整体占位
          符内容 #prodId#
        • search函数找到了第一个符合的之后,它就不会再继续往后去查找
"""
*
re模块函数 : findall(),search()
    * findall(正则表达式, 目标字符串) ,字符串从头到尾去匹配,只要符合要求就会拿出来
        * 多个符合的结果 存到列表里,返回一个列表。
        * 如果只想获取里面的变量名 不需要这个## 限定符,可以加个括号括起来: 加了括号,就是只提取括号里的内容,## 就是左右边界了。
    * search(正则表达式, 目标字符串)方法
        * 搜索查找,如果匹配的正则符合的话,返回Match类型的对象,不符合就返回None
        * Match类型的对象(search函数的返回值)的方法group() - 正则分组,可以返回的匹配的整体子串-整体占位符内容 #prodId#
        * search函数找到了第一个符合的之后,它就不会再继续往后去查找
            - 循环找: 先找到第一个,替换掉这个; 继续第二次,替换掉,第三次。。。。
            - 确定循环次数么?--不能确认循环次数的时候,用while。
            - while循环先进去,找不到占位符 -- 返回值None ,跳出循环。

思路:
1、环境变量里已经有了 属性名 --属性值
class EnviData:
    prodId = 7717
    skuId = 4563
2、先从类里获取属性值 -- getattr(EnviData,prodId) === prodId这个名字
3、先用正则找到这个字符串里的 占位符标记的字符串 ,replace(标记符,类属性值)

注意: 做的事情
- 第一个: 取到 #prodId#  == 被替换掉的子字符串
- 第二个: prodId  == 获取类属性变量。
"""

import re

str_data = '{"basketId": 0, "count": 1, "prodId": #prodId#, "shopId": 1, "skuId": #skuId#}'

# findall(正则表达式, 目标字符串) ,字符串从头到尾去匹配,只要符合要求就会拿出来
result = re.findall("#.*?#",str_data) # 结果是  #prodId#
print(result)
# 得到## 限定符里面的变量名:--加上括号就只会得到 #里面的值#
var_name = re.findall("#(.*?)#",str_data) #结果  prodId
print(var_name)

# search(正则表达式, 目标字符串)方法
result = re.search("#(.*?)#",str_data) # 结果是一个对象
print(result)
# 对象的group()方法 - 正则分组,可以返回的匹配的整体子串
value = result.group()  # 结果是  #prodId#
print(value)
var_name = result.group(1) # 第一个分组的值 结果是 prodId
print(var_name)


# 没有匹配到结果--None
result = re.search("#.*?!#",str_data) # 结果是一个对象
print(result)

运行结果如下:

['#prodId#', '#skuId#']
['prodId', 'skuId']
<re.Match object; span=(38, 46), match='#prodId#'>
#prodId#
prodId
None

循环正则提取

"""
*
re模块函数 : findall(),search()
    * findall(正则表达式, 目标字符串) ,字符串从头到尾去匹配,只要符合要求就会拿出来
        * 多个符合的结果 存到列表里,返回一个列表。
        * 如果只想获取里面的变量名 不需要这个## 限定符,可以加个括号括起来: 加了括号,就是只提取括号里的内容,## 就是左右边界了。
    * search(正则表达式, 目标字符串)方法
        * 搜索查找,如果匹配的正则符合的话,返回Match类型的对象,不符合就返回None
        * Match类型的对象(search函数的返回值)的方法group() - 正则分组,可以返回的匹配的整体子串-整体占位符内容 #prodId#
        * search函数找到了第一个符合的之后,它就不会再继续往后去查找
            - 循环找: 先找到第一个,替换掉这个; 继续第二次,替换掉,第三次。。。。
            - 确定循环次数么?--不能确认循环次数的时候,用while。
            - while循环先进去,找不到占位符 -- 返回值None ,跳出循环。

思路:
1、环境变量里已经有了 属性名 --属性值
class EnviData:
    prodId = 7717
    skuId = 4563
2、先从类里获取属性值 -- getattr(EnviData,prodId) === prodId这个名字
3、先用正则找到这个字符串里的 占位符标记的字符串 ,replace(标记符,类属性值)

注意: 做的事情
- 第一个: 取到 #prodId#  == 被替换掉的子字符串
- 第二个: prodId  == 获取类属性变量。
"""

import re

str_data = '{"basketId": 0, "count": 1, "prodId": #prodId#, "shopId": 1, "skuId": #skuId#}'
class EnviData:
    prodId = 7717
    skuId = 4563


while True:
    # search(正则表达式, 目标字符串)方法
    result = re.search("#(.*?)#",str_data) # 结果是一个对象
    if result is None:  # 如果没有占位符 就是None 跳出循环
        break
    mark = result.group()  # 结果是  #prodId# --要被替换的子字符串
    var_name = result.group(1) # 第一个分组的值 结果是 prodId
    # 从环境变量里获取变量名对应的属性值  prodId-- 属性值 7717,替换的数据
    var_value = getattr(EnviData,var_name)  # 结果 : 7717--int类型
    # 目标字符串的替换,赋值给一个变量
    # 注意:replace方法要求 两个参数都应该是字符串。
    str_data = str_data.replace(mark,str(var_value))
    print(str_data)

运行结果如下:

{"basketId": 0, "count": 1, "prodId": 7717, "shopId": 1, "skuId": #skuId#}
{"basketId": 0, "count": 1, "prodId": 7717, "shopId": 1, "skuId": 4563}

多接口动态参数封装

提取响应结果的函数 – 是在接口执行完成后 得到响应结果
都需要在requests_api函数里扩展 ==对应的位置调用这两个函数就可以了。

提取响应结果的函数 – 是在接口执行完成后 得到响应结果

handle_replace.py 内容如下:

"""
1、def封装
2、参数化
3、返回值: 最终要拿到替换后的字符串 ---  头部 参数 要用于发送接口测试的
4、加上日志: 但凡你想确认数据结果的地方 都可以加上日志
5、因为有些接口不需要做数据提取,所以判空处理:
6、异常捕获: 因为有可能环境变量里没有这个属性名 和属性值

"""
import re
from loguru import logger
from tools.envi_data import EnviData


def replace_mark(str_data):
    while True:
        if str_data is None:
            return
        result = re.search("#(.*?)#",str_data)
        if result is None:  # 如果没有占位符 就是None 跳出循环
            break
        mark = result.group()  # 结果是  #prodId# --要被替换的子字符串
        logger.info(f"要被替换的子字符串:{mark}")
        var_name = result.group(1) # 第一个分组的值 结果是 prodId
        logger.info(f"要提取环境变量的属性名:{var_name}")
        try:
            var_value = getattr(EnviData,var_name)  # 结果 : 7717--int类型
        except AttributeError as e:
            logger.error(f"环境变量里不存在这个属性:{var_name}")
            raise e
        logger.info(f"要提取环境变量的属性值:{var_value}")
        str_data = str_data.replace(mark,str(var_value))
        logger.info(f"替换完成后的字符串是:{str_data}")
    return str_data

if __name__ == '__main__':
    str_data = '{"basketId": 0, "count": 1, "prodId": #prodId#, "shopId": 1, "skuId": #skuId#}'
    replace_mark(str_data)

替换头部和参数里的占位符的函数 – 是在发送接口请求之前

envi_data.py内容如下:


# 这个类就是为了存储环境变量 实现环境变量的共享的
class EnviData:
    pass

handle_extract.py 内容(获取需要提取的字段名)如下:

"""
1、def封装
2、参数化
3、返回值: 因为数据都存在环境变量 所以不需要返回值
4、加上日志: 但凡你想确认数据结果的地方 都可以加上日志
5、因为有些接口不需要做数据提取,所以判空处理:

"""


import json
from jsonpath import jsonpath
from loguru import logger
from tools.envi_data import EnviData


def extract_response(response,extract_data):
    # 因为有些接口不需要做数据提取,所以判空处理:
    if extract_data is None:
        logger.info("这条用例不需要做响应结果的数据提取!")
        return
    # 第一步: 反序列化 -字典
    logger.info("-----------------响应结果提取开始------------------------------")
    extract_data = json.loads(extract_data)
    logger.info(f"提取的向红结果的表达式是:{extract_data}")
    for k,v in extract_data.items():  # k 是access_token 变量名字,v是$..access_token
        # 使用jsonpath表达式 提取login响应结果里的值
        value = jsonpath(response.json(),v)[0]  # 是access_token的具体值
        # 存起来到环境变量里去
        setattr(EnviData,k,value)
    logger.info(f"提取并设置环境变量之后的类属性是:{EnviData.__dict__}")


if __name__ == '__main__':
    response = {"access_token": "0efdce50-0e2f-4ed0-b4d1-944be5ab518a",
                "token_type": "bearer", "refresh_token": "4bfc3638-e7e4-4844-a83d-c0f8340bc146",
                "expires_in": 1295999,
                "pic": "http://mall.lemonban.com:8108/2023/09/b5a479b28d514aa59dfa55422b23a6f0.jpg",
                "userId": "46189bfd628e4a738f639017f1d9225d", "nickName": "lemon_auto", "enabled": True}

    extract_data = '{"access_token":"$..access_token","token_type":"$..token_type"}'
    extract_response(response,extract_data)

handle_requests.py文件如下:

"""
方法优化:
1、日志加上

2、测试用例方法里调用夹具 获取返回值。
- 更新requests-api,需要做token处理:
 - 设置一个默认参数:token = None
 - 如果接口需要鉴权,测试用例里调用夹具,得到token,requests_api传递token参数;--requests 更新头部
 - 如果接口不需要鉴权: token不传  None。 不会做更新头部的操作。

"""



import json
import requests
from tools.handle_path import pic_path
from loguru import logger
from tools.handle_extract import extract_response
from tools.handle_replace import replace_mark


def requests_api(casedata,token=None):
    method = casedata["请求方法"]
    url = casedata["接口地址"]
    headers = casedata["请求头"]
    params = casedata["请求参数"]
    # 在发送请求之前完成头部和参数的替换--调用替换的函数==结果是字符串
    headers = replace_mark(headers)
    params = replace_mark(params)
    # 反序列操作: 结合判空处理,
    if headers is not None:
        headers = json.loads(headers)
        if token is not None:  # 这是做接口如果需要鉴权,传进来token 更新头部信息。
            headers["Authorization"] = token  # 字典新增 / 修改
    if params is not None:
        params = json.loads(params)
    logger.info("---------------------------请求消息-----------------------------------")
    logger.info(f"请求方法是{method}")
    logger.info(f"请求地址是{url}")
    logger.info(f"请求头部是{headers}")
    logger.info(f"请求参数是{params}")
    #接口请求可能是get  post  put等各种请求方法 分支判断
    if method.lower() == "get":
        resp = requests.request(method=method, url=url, params=params,headers=headers)
    elif method.lower() == "post":
        if headers is None:
            logger.info("头部为空,检查excel表格里头部信息!")
            return
        # post请求:content-type的类型有关系。需要对每一种类型做处理 分支判断
        if headers["Content-Type"] == "application/json":
            resp = requests.request(method=method, url=url, json=params, headers=headers)
        if headers["Content-Type"] == "application/x-www-form-urlencoded":
            resp = requests.request(method=method, url=url, data=params, headers=headers)
        if headers["Content-Type"] == "multipart/form-data":
            # 发送请求的时候不能带上  'Content-Type': 'multipart/form-data'  删除之后才发送接口请求。
            headers.pop("Content-Type") # 字典删除元素
            filename = params["filename"]  # 文件名字 值
            file_obj = {"file": (filename, open(pic_path/filename, "rb"))}  # 文件参数
            logger.info(f"文件接口的参数是:{file_obj}")
            logger.info(f"文件接口的头部是:{headers}")
            resp = requests.request(method=method, url=url,headers=headers,files=file_obj)
    elif method.lower() == "put":
        pass
    logger.info("------------------------------响应消息-----------------------------")
    logger.info(f"接口响应状态码是:{resp.status_code}")
    logger.info(f"接口响应体是:{resp.text}")
    # 提取响应结果的数据-- 调用提取数据的函数
    extract_response(resp,casedata["提取响应字段"])
    return resp

  • 12
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值