API 接口自动化测试详细图文教程学习系列24--如何用Pytest去设计接口测试用例并执行

测试学习记录,仅供参考!

如何用Pytest去设计接口测试用例

优化代码,添加前后置操作处理,防止重复写入数据

1、先删除 testcase 软件包的 login 和 userManager 目录中的测试文件;

2、在 login 目录下新建 test_login.py 文件,并输入以下内容;

复制代码
# 导包--引入pytest模块框架
import pytest
# 导入 读取 yaml 文件数据模块--读取文件模块
from unit_tools.handle_data.yaml_handler import read_yaml, write_yaml
# 导入读取配置文件模块--解析配置文件
from unit_tools.handle_data.configParse import ConfigParser
# 引入 发起接口请求 模块
from unit_tools.sendrequests import SendRequests

# 在最外层先 实例化一个对象--读取配置文件
conf = ConfigParser()


# 创建一个测试类
class TestLogin:
    """登录模块"""

    # 参数化--通过参数化去读取到接口信息
    @pytest.mark.parametrize('api_info', read_yaml('./datas/login.yaml'))
    # 创建一个测试用例
    def test_login_module(self, api_info):
        # 从yaml文件获取接口信息

        # conf.get_host('host') 拿到服务器地址--再返回赋值给conf_url
        conf_url = conf.get_host('host')
        url = conf_url + api_info['baseInfo']['url']
        method = api_info['baseInfo']['method']
        # 这里登录接口做特殊处理了--目前不需要请求头,所以暂时传空
        header = None
        # 入参
        req_param = api_info['testCase'][0]['data']

        # 调用接口SendRequests类去执行接口请求
        # 先实例化这个类--然后给它返回一个对象
        send = SendRequests()
        # 再通过这个对象去调用 执行接口请求 方法--里面开始传参
        response = send.execute_api_request(api_name=None, url=url, method=method, header=header, case_name=None,
                                            cookie=None, file=None, data=req_param)
        # 将接口返回值转换为json格式
        result_json = response.json()
        print(f'接口实际返回信息:{result_json}')

        # 把登录接口的返回值token写入到extract.yaml文件中
        login_token = {}
        login_token['token'] = result_json['token']
        write_yaml(login_token)

        # 结果断言
        assert result_json['msg'] == '登录成功', '登录接口测试失败!'

3、执行主函数,运行 run.py 文件,查看执行结果不尽理想,多次执行后会重复出现写入数据,继续下一步;

4、修改 datas 目录下 adduser.yaml 文件内容;

复制代码
- baseInfo:
    api_name: 用户新增
    url: /dar/user/addUser
    method: post
    header: ${get_headers(data)}
  testCase:
     - case_name: 用户新增正常校验
       data:
         username: testadduser
         password: tset6789890
         role_id: 123456789
         dates: '2000-12-31'
         phone: 13800000000
         token: ${get_extract_data(token)}

5、在项目根目录下新建 conftest.py 文件,并输入以下内容;增加前后置操作,在测试用例执行前,把 extract.yaml 文件给清空;

复制代码
# 导包
import pytest
from unit_tools.handle_data.yaml_handler import clear_yaml

@pytest.fixture(scope='session', autouse=True)
def clear_extract():
    clear_yaml()

6、在userManager 目录下新建 test_user_manager.py 文件,并输入以下内容;

复制代码
# 导包
import pytest
from unit_tools.handle_data.yaml_handler import read_yaml
from unit_tools.handle_data.configParse import ConfigParser
from unit_tools.sendrequests import SendRequests
# 引入 动态解析 模块
from unit_tools.apiutils import RequestsBase

conf = ConfigParser()
# 也做个实例化
parse = RequestsBase()

class TestUserManager:
    """用户管理模块"""

    @pytest.mark.parametrize('api_info', read_yaml('./datas/adduser.yaml'))
    def test_user_add(self, api_info):

        # 解析yaml文件里面的${......},得到一个调用后的接口信息
        api_info = parse.parse_and_replace_variables(api_info)

        # 从解析后的接口信息中获取接口信息
        conf_url = conf.get_host('host')
        url = conf_url + api_info['baseInfo']['url']
        method = api_info['baseInfo']['method']
        # header = eval(api_info['baseInfo']['header'])
        header = None
        req_param = api_info['testCase'][0]['data']

        # 调用接口SendRequests类去执行接口请求
        send = SendRequests()
        # 再通过这个对象去调用 执行接口请求 方法--里面开始传参
        response = send.execute_api_request(api_name=None, url=url, method=method, header=header, case_name=None, cookie=None, file=None, data=req_param)
        # 将接口返回值转换为json格式
        result_json = response.json()
        print(f'接口实际返回信息:{result_json}')

        # 结果断言
        assert result_json['msg'] == '新增成功', '新增接口测试失败!'

7、再执行主函数,运行 run.py 文件,多次重复执行后不会出现重复数据信息了;

约定 yaml 文件接口信息标准格式

8、接口信息的yaml文件格式约定,例如:login.yaml文件;

复制代码
- baseInfo:
    api_name: 用户登录
    url: /dar/user/login
    method: post
    header:
        Content-Type: application/x-www-formurlencoded;charset=UTF-8
    cookies:
      Cookie: test123456789020241011
  testCase:
     - case_name: 用户正常登录校验
       data:
         user_name: test01
         passwd: admin123
       files:
         file: ./data/login.yaml
       validation:
         - contain: {'msg':'登录成功'}
         - eq: {'msg':'登录成功'}
       extract:
         token: $.token
       extract_list:
         goodid: $.good_id

文件格式约定,关键词内容说明:

yaml文件必须包含 baseInfo和testCase ,最外面 "- "(一个横杠)表示数据是一个list类型;

baseInfo下面的关键词

  • api_name : 接口名称;
  • url : 接口地址;
  • method : 请求方式;
  • header (可选) : 请求头,根据实际接口 决定是否填写;
  • cookies (可选) : cookie,根据实际项目情况决定;

testCase下面的关键词

  • case_name : 测试用例名称
  • data/params/json(可选) : 请求参数数据类型,根据接口请求头决定写什么类型,一般情况下get请求就写params,如果是post请求,这时要根据请求头是表单提交就写data,若是json格式提交就填写json;
  • files(可选):文件上传接口;
  • validation : 断言结果,有多种断言模式,如:包含断言 contain、相等断言 eq、不相等断言 nq、数据库断言 db;
  • extract (可选) : 提取单个值,接口的返回值提取,存放在extract.yaml 全局文件,给别的接口使用,达到一个接口之间的上下游参数传递的目的;
  • extract_list (可选) : 提取多个值,接口的返回值提取,存放在extract.yaml,给别的接口使用,达到一个接口之间的上下游参数传递的目的;

封装请求接口

9、这里写在了项目根目录 unit_tools 软件包下的 apiutils.py 文件中,修改 apiutils.py 文件内容;

复制代码
# 导包
import json
from unit_tools.handle_data.yaml_handler import read_yaml
import re
from unit_tools.debugtalk import DebugTalk
# 引入 配置文件模块
from unit_tools.handle_data.configParse import ConfigParser

# 创建一个类 RequestsBase
class RequestsBase:

    # 在RequestsBase类的里面写一个初始化构造函数
    def __init__(self):
        # 实例化一下,实例化之后就可以通过这个对象去获取 host
        self.conf = ConfigParser()

    # 创建一个方法 parse_and_replace_variables(解析并且替换变量)--传一个参数 yml_data--要解析的yaml文件的数组
    def parse_and_replace_variables(self, yml_data):
        """
        解析并替换YAML文件数据中的变量引用,如:${get_extract_data(goodsId,1)}
        :param yml_data: 解析的YAML数据
        :return: 最终要返回的是dict类型--才能给后续的一些方法调用时使用
        """
        # if isinstance(yml_data, str)--判断传进来的数据是什么类型?--后续需要字符串才能做操作的,所以这里先判断是不是字符串格式
        # yml_data若是字符串则执行 if 语句左边的代码 直接返回yml_data
        # yml_data不是字符串--调用json.dumps(yml_data)模块转成字符串--ensure_ascii=False 参数是可以处理中文
        # if else 语句写在一行是 三元运算--通过这一系列之后最终返回一个 新的变量yaml_data_str --它是一个字符串
        yml_data_str = yml_data if isinstance(yml_data, str) else json.dumps(yml_data, ensure_ascii=False)
        # 打印查看 yaml_data_str
        print(f'解析前:{yml_data_str}')
        # 用for循环去判断 字符串yml_data_str里面这种标识 "${" 格式出现的次数--加一个'_'下划线表示不需要用到这个变量,是一个占位符的意思
        for _ in range(yml_data_str.count("${")):
            # if判断这个标识'${' 它包含在字符串yml_data_str里面--并且另一半它 '}' 也包含在字符串yml_data_str里面
            if '${' in yml_data_str and '}' in yml_data_str:
                # 通过获取到 "${" 标识的起始位置--通过字符串 yml_data_str 的索引 index--获取它的索引的起始位置
                strat_index = yml_data_str.index("${")
                # 获取结束位置 标识 "}"--再加一个参数 它的开头 strat_index
                end_index = yml_data_str.index("}", strat_index)
                # 拿到开始、结束位置后--通过字符串切片--起始位置、结束位置+1--为什么+1?--因为python中索引切片是左闭右开的一个规则
                # 什么是左闭右开?--开始位置是包含在切片里面的,但是结束元素不包含在切片中,所以得+1
                variable_data = yml_data_str[strat_index:end_index + 1]
                # print(f'函数名:{variable_data}')

                # 使用正则表达式提取函数名和参数
                # 导入re模块--调用re模块中的match方法--里面跟两个参数,第一个参数是正则表达式,第二个参数是要提取正则表达式的字符串
                match = re.match(r'\$\{(\w+)\((.*?)\)\}', variable_data)
                # 判断一下有没有匹配到--匹配的对象是不是成功了--成功
                if match:
                    # 通过match.groups()去拿到函数名还有参数--定义两个变量去接收,第一个函数名func_name、第二个函数值func_params
                    func_name, func_params = match.groups()

                    # 先判断它存在--走if左边的代码--给它做一个切片;若参数值不存在,则返回一个空列表即可
                    # 参数值有一个或多个--所以需要做一个处理--参数值func_params通过逗号去做一个切片
                    # 最后再把值重新赋值给它自己
                    func_params = func_params.split(',') if func_params else []
                    # print(f'函数值:{func_name}')

                    # 使用面向对象反射getattr调用函数
                    # 引如类后直接 实例化类--第二个参数传的是一个函数名 func_name--函数调用 (*func_params)--通过星号把参数值func_params去解包传递给参数func_name
                    extract_data = getattr(DebugTalk(), func_name)(*func_params)
                    print(f'提取到的结果:{extract_data}')

                    # 使用正则表达式替换原始字符串中的变量引用为调用后的结果
                    # 使用正则表达式替换函数re.sub--第一个参数就是需要替换的哪个参数值,第二个参数是解析后得到的数据extract_data,第三个参数是原始解析前的yml_data_str
                    yml_data_str = re.sub(re.escape(variable_data), str(extract_data), yml_data_str)
        # 还原数据,将其转换为字典类型
        # 添加异常处理--像这种转换的最好是加一个异常处理
        try:
            # 直接调用 son.loads(yml_data_str)--把字符串转换成字典
            data = json.loads(yml_data_str)
        # json转码异常
        except json.JSONDecodeError:
            # 返回原始数据yml_data_str--赋值给新变量 data
            data = yml_data_str
        return data

    # 写个方法 execute_test_cases--传一个参数 接口的信息--即写yaml文件信息
    def execute_test_cases(self, api_info):
        """
        规范yaml接口信息,执行接口、提取结果以及断言操作
        :param api_info:
        :return:
        """
        # 先打印查看api_info信息
        print(api_info)
        # 添加异常
        try:
            # 获取到接口的服务器地址--通过读取配置文件去获取--通过这个对象去调用
            conf_host = self.conf.get_host('host')
            # 获取url--完整的url是通过 服务器地址    加上  接口信息api_info
            url = conf_host + api_info['baseInfo']['url']
            # 打印url
            print(url)
        except Exception as e:
            # 因暂时未写日志模块,所以这里先打印出来
            print(f'出现未知异常:--{e}')


# 调试查看
if __name__ == '__main__':
    # 先引进读取方法 read_yaml--传如一个它的相对路径--赋值给新变量data--[0]通过索引去列表值第一个
    api_info = read_yaml('.././datas/login.yaml')[0]
    # 实例化类 RequestsBase()--并赋值给一个变量对象 req
    req = RequestsBase()

    # 通过这个对象req去调用刚刚封装的方法execute_test_cases--把读取到yaml文件的数据api_info传给它
    req.execute_test_cases(api_info)

10、运行 apiutils.py 文件,从结果中拿到了接口的完整地址,至此已经成功拿到了 URL;可再继续拿到其他关键词信息,例如:接口名称、请求方式、请求头......等等所需的数据信息;

获取请求头信息讲解
方式一、固定内容

11、,如若 header 是固定写死的,怎么处理?

复制代码
    header:
        Content-Type: application/x-www-formurlencoded;charset=UTF-8

部分代码示例(详情步骤参考如下,烦请自行查看):

复制代码
# 请求头,通过get方法获取看里面有没有请求头;如果有的话就返回给变量header,若没有,则传个默认空值None就行
# 判断请求头是否可选,使用get的话,当header有的话就返回,没有时就返回None,不至于报错
header = api_info['baseInfo'].get('header', None)
# 再进行一步处理--判断 header 不为空
if header is not None:
    # 判断header是什么类型?如果是一个字符串str类型的话 就需要给它做一个解析
    # if isinstance(header, str)    
    # 调用parse_and_replace_variables方法,把header给传进来
    # self.parse_and_replace_variables(header) 
    # 若不是字符串,直接给他返回header
    # else header   
    # header =    最后再把结果传回给它自己就行了
    header = self.parse_and_replace_variables(header) if isinstance(header, str) else header
    # 打印查看--type() 打印类型
    print(header, type(header))

# 结果--它是一个dict字典类型
{'Content-Type': 'application/x-www-formurlencoded;charset=UTF-8'} <class 'dict'>    

它是一个字符串类型,因为要把键值对的形式发送到接口请求里面,接口请求它只能接收一个字典,所以需要转换成字典类型;这样去把请求头发送给request模块,它才能正确的去解析这个请求,因为它只能去接收一个字典类型;此时,无论header是固定写死的,还是通过动态解析变量引用的,它都是字典类型;如果header是可选的,配置文件中就没写,如果header不填的话,它就返回默认空值,它就不执行if后面的语句了。

方式二、动态解析

12、如若是通过动态解析变量引用的方式, 则需要进一步处理,需要多判断一下;

13、 修改 login.yaml 文件内容,设置为通过动态解析变量引用方式;

复制代码
- baseInfo:
    api_name: 用户登录
    url: /dar/user/login
    method: post
    header:
        ${get_headers(data)}
    cookies:
      Cookie: test123456789020241011
  testCase:
     - case_name: 用户正常登录校验
       data:
         user_name: test01
         passwd: admin123
       validation:
         - contain: {'msg':'登录成功'}
         - eq: {'msg':'登录成功'}
       extract:
         token: $.token
       extract_list:
         goodid: $.good_id

14、继续修改 apiutils.py 文件内容,增加 if 判断;

复制代码
# 导包
import json
from unit_tools.handle_data.yaml_handler import read_yaml
import re
from unit_tools.debugtalk import DebugTalk
# 引入 配置文件模块
from unit_tools.handle_data.configParse import ConfigParser

# 创建一个类 RequestsBase
class RequestsBase:

    # 在RequestsBase类的里面写一个初始化构造函数
    def __init__(self):
        # 实例化一下,实例化之后就可以通过这个对象去获取 host
        self.conf = ConfigParser()

    # 创建一个方法 parse_and_replace_variables(解析并且替换变量)--传一个参数 yml_data--要解析的yaml文件的数组
    def parse_and_replace_variables(self, yml_data):
        """
        解析并替换YAML文件数据中的变量引用,如:${get_extract_data(goodsId,1)}
        :param yml_data: 解析的YAML数据
        :return: 最终要返回的是dict类型--才能给后续的一些方法调用时使用
        """
        # if isinstance(yml_data, str)--判断传进来的数据是什么类型?--后续需要字符串才能做操作的,所以这里先判断是不是字符串格式
        # yml_data若是字符串则执行 if 语句左边的代码 直接返回yml_data
        # yml_data不是字符串--调用json.dumps(yml_data)模块转成字符串--ensure_ascii=False 参数是可以处理中文
        # if else 语句写在一行是 三元运算--通过这一系列之后最终返回一个 新的变量yaml_data_str --它是一个字符串
        yml_data_str = yml_data if isinstance(yml_data, str) else json.dumps(yml_data, ensure_ascii=False)
        # 打印查看 yaml_data_str
        print(f'解析前:{yml_data_str}')
        # 用for循环去判断 字符串yml_data_str里面这种标识 "${" 格式出现的次数--加一个'_'下划线表示不需要用到这个变量,是一个占位符的意思
        for _ in range(yml_data_str.count("${")):
            # if判断这个标识'${' 它包含在字符串yml_data_str里面--并且另一半它 '}' 也包含在字符串yml_data_str里面
            if '${' in yml_data_str and '}' in yml_data_str:
                # 通过获取到 "${" 标识的起始位置--通过字符串 yml_data_str 的索引 index--获取它的索引的起始位置
                strat_index = yml_data_str.index("${")
                # 获取结束位置 标识 "}"--再加一个参数 它的开头 strat_index
                end_index = yml_data_str.index("}", strat_index)
                # 拿到开始、结束位置后--通过字符串切片--起始位置、结束位置+1--为什么+1?--因为python中索引切片是左闭右开的一个规则
                # 什么是左闭右开?--开始位置是包含在切片里面的,但是结束元素不包含在切片中,所以得+1
                variable_data = yml_data_str[strat_index:end_index + 1]
                # print(f'函数名:{variable_data}')

                # 使用正则表达式提取函数名和参数
                # 导入re模块--调用re模块中的match方法--里面跟两个参数,第一个参数是正则表达式,第二个参数是要提取正则表达式的字符串
                match = re.match(r'\$\{(\w+)\((.*?)\)\}', variable_data)
                # 判断一下有没有匹配到--匹配的对象是不是成功了--成功
                if match:
                    # 通过match.groups()去拿到函数名还有参数--定义两个变量去接收,第一个函数名func_name、第二个函数值func_params
                    func_name, func_params = match.groups()

                    # 先判断它存在--走if左边的代码--给它做一个切片;若参数值不存在,则返回一个空列表即可
                    # 参数值有一个或多个--所以需要做一个处理--参数值func_params通过逗号去做一个切片
                    # 最后再把值重新赋值给它自己
                    func_params = func_params.split(',') if func_params else []
                    # print(f'函数值:{func_name}')

                    # 使用面向对象反射getattr调用函数
                    # 引如类后直接 实例化类--第二个参数传的是一个函数名 func_name--函数调用 (*func_params)--通过星号把参数值func_params去解包传递给参数func_name
                    extract_data = getattr(DebugTalk(), func_name)(*func_params)
                    print(f'提取到的结果:{extract_data}')

                    # 使用正则表达式替换原始字符串中的变量引用为调用后的结果
                    # 使用正则表达式替换函数re.sub--第一个参数就是需要替换的哪个参数值,第二个参数是解析后得到的数据extract_data,第三个参数是原始解析前的yml_data_str
                    yml_data_str = re.sub(re.escape(variable_data), str(extract_data), yml_data_str)
        # 还原数据,将其转换为字典类型
        # 添加异常处理--像这种转换的最好是加一个异常处理
        try:
            # 直接调用 son.loads(yml_data_str)--把字符串转换成字典
            data = json.loads(yml_data_str)
        # json转码异常
        except json.JSONDecodeError:
            # 返回原始数据yml_data_str--赋值给新变量 data
            data = yml_data_str
        return data

    # 写个方法 execute_test_cases--传一个参数 接口的信息--即写yaml文件信息
    def execute_test_cases(self, api_info):
        """
        规范yaml接口信息,执行接口、提取结果以及断言操作
        :param api_info:
        :return:
        """
        # 先打印查看api_info信息
        print(api_info)
        # 添加异常
        try:
            # 获取到接口的服务器地址--通过读取配置文件去获取--通过这个对象去调用
            conf_host = self.conf.get_host('host')
            # 获取url--完整的url是通过 服务器地址    加上  接口信息api_info
            url = conf_host + api_info['baseInfo']['url']
            # 接口名称
            api_name = api_info['baseInfo']['api_name']
            # 请求方式
            method = api_info['baseInfo']['method']
            # 请求头--通过get方法获取看里面有没有请求头--如果有的话就返回给变量header,若没有,则传个默认空值None就行
            # 判断请求头是否可选,使用get的话,当header有的话就返回,没有时就返回None,不至于报错
            header = api_info['baseInfo'].get('header', None)
            # 再进行一步处理--判断 header 不为空
            if header is not None:
                # if isinstance(header, str)    判断header是什么类型?--如果是一个字符串str类型的话 就需要给它做一个解析
                # self.parse_and_replace_variables(header)  调用parse_and_replace_variables方法,把header给传进来
                # else header   若不是字符串,直接给他返回header
                # header =    最后再把结果传回给它自己就行了
                # eval()    调用一个函数 把解析后的数据 self.parse_and_replace_variables(header) 转换为 字典类型
                header = eval(self.parse_and_replace_variables(header)) if isinstance(header, str) else header
                # 打印查看--type() 打印类型
                print(header, type(header))
        except Exception as e:
            # 因暂时未写日志模块,所以这里先打印出来
            print(f'出现未知异常:--{e}')


# 调试查看
if __name__ == '__main__':
    # 先引进读取方法 read_yaml--传如一个它的相对路径--赋值给新变量data--[0]通过索引去列表值第一个
    api_info = read_yaml('.././datas/login.yaml')[0]
    # 实例化类 RequestsBase()--并赋值给一个变量对象 req
    req = RequestsBase()

    # 通过这个对象req去调用刚刚封装的方法execute_test_cases--把读取到yaml文件的数据api_info传给它
    req.execute_test_cases(api_info)

15、到这一步,请求头已经处理完了,继续处理下一个 cookie 字段信息,与 header一样处理;

复制代码
# 导包
import json
from unit_tools.handle_data.yaml_handler import read_yaml
import re
from unit_tools.debugtalk import DebugTalk
# 引入 配置文件模块
from unit_tools.handle_data.configParse import ConfigParser

# 创建一个类 RequestsBase
class RequestsBase:

    # 在RequestsBase类的里面写一个初始化构造函数
    def __init__(self):
        # 实例化一下,实例化之后就可以通过这个对象去获取 host
        self.conf = ConfigParser()

    # 创建一个方法 parse_and_replace_variables(解析并且替换变量)--传一个参数 yml_data--要解析的yaml文件的数组
    def parse_and_replace_variables(self, yml_data):
        """
        解析并替换YAML文件数据中的变量引用,如:${get_extract_data(goodsId,1)}
        :param yml_data: 解析的YAML数据
        :return: 最终要返回的是dict类型--才能给后续的一些方法调用时使用
        """
        # if isinstance(yml_data, str)--判断传进来的数据是什么类型?--后续需要字符串才能做操作的,所以这里先判断是不是字符串格式
        # yml_data若是字符串则执行 if 语句左边的代码 直接返回yml_data
        # yml_data不是字符串--调用json.dumps(yml_data)模块转成字符串--ensure_ascii=False 参数是可以处理中文
        # if else 语句写在一行是 三元运算--通过这一系列之后最终返回一个 新的变量yaml_data_str --它是一个字符串
        yml_data_str = yml_data if isinstance(yml_data, str) else json.dumps(yml_data, ensure_ascii=False)
        # 打印查看 yaml_data_str
        print(f'解析前:{yml_data_str}')
        # 用for循环去判断 字符串yml_data_str里面这种标识 "${" 格式出现的次数--加一个'_'下划线表示不需要用到这个变量,是一个占位符的意思
        for _ in range(yml_data_str.count("${")):
            # if判断这个标识'${' 它包含在字符串yml_data_str里面--并且另一半它 '}' 也包含在字符串yml_data_str里面
            if '${' in yml_data_str and '}' in yml_data_str:
                # 通过获取到 "${" 标识的起始位置--通过字符串 yml_data_str 的索引 index--获取它的索引的起始位置
                strat_index = yml_data_str.index("${")
                # 获取结束位置 标识 "}"--再加一个参数 它的开头 strat_index
                end_index = yml_data_str.index("}", strat_index)
                # 拿到开始、结束位置后--通过字符串切片--起始位置、结束位置+1--为什么+1?--因为python中索引切片是左闭右开的一个规则
                # 什么是左闭右开?--开始位置是包含在切片里面的,但是结束元素不包含在切片中,所以得+1
                variable_data = yml_data_str[strat_index:end_index + 1]
                # print(f'函数名:{variable_data}')

                # 使用正则表达式提取函数名和参数
                # 导入re模块--调用re模块中的match方法--里面跟两个参数,第一个参数是正则表达式,第二个参数是要提取正则表达式的字符串
                match = re.match(r'\$\{(\w+)\((.*?)\)\}', variable_data)
                # 判断一下有没有匹配到--匹配的对象是不是成功了--成功
                if match:
                    # 通过match.groups()去拿到函数名还有参数--定义两个变量去接收,第一个函数名func_name、第二个函数值func_params
                    func_name, func_params = match.groups()

                    # 先判断它存在--走if左边的代码--给它做一个切片;若参数值不存在,则返回一个空列表即可
                    # 参数值有一个或多个--所以需要做一个处理--参数值func_params通过逗号去做一个切片
                    # 最后再把值重新赋值给它自己
                    func_params = func_params.split(',') if func_params else []
                    # print(f'函数值:{func_name}')

                    # 使用面向对象反射getattr调用函数
                    # 引如类后直接 实例化类--第二个参数传的是一个函数名 func_name--函数调用 (*func_params)--通过星号把参数值func_params去解包传递给参数func_name
                    extract_data = getattr(DebugTalk(), func_name)(*func_params)
                    print(f'提取到的结果:{extract_data}')

                    # 使用正则表达式替换原始字符串中的变量引用为调用后的结果
                    # 使用正则表达式替换函数re.sub--第一个参数就是需要替换的哪个参数值,第二个参数是解析后得到的数据extract_data,第三个参数是原始解析前的yml_data_str
                    yml_data_str = re.sub(re.escape(variable_data), str(extract_data), yml_data_str)
        # 还原数据,将其转换为字典类型
        # 添加异常处理--像这种转换的最好是加一个异常处理
        try:
            # 直接调用 son.loads(yml_data_str)--把字符串转换成字典
            data = json.loads(yml_data_str)
        # json转码异常
        except json.JSONDecodeError:
            # 返回原始数据yml_data_str--赋值给新变量 data
            data = yml_data_str
        return data

    # 写个方法 execute_test_cases--传一个参数 接口的信息--即写yaml文件信息
    def execute_test_cases(self, api_info):
        """
        规范yaml接口信息,执行接口、提取结果以及断言操作
        :param api_info:
        :return:
        """
        # 先打印查看api_info信息
        print(api_info)
        # 添加异常
        try:
            # 获取到接口的服务器地址--通过读取配置文件去获取--通过这个对象去调用
            conf_host = self.conf.get_host('host')
            # 获取url--完整的url是通过 服务器地址    加上  接口信息api_info
            url = conf_host + api_info['baseInfo']['url']
            # 接口名称
            api_name = api_info['baseInfo']['api_name']
            # 请求方式
            method = api_info['baseInfo']['method']
            # 请求头--通过get方法获取看里面有没有请求头--如果有的话就返回给变量header,若没有,则传个默认空值None就行
            # 判断请求头是否可选,使用get的话,当header有的话就返回,没有时就返回None,不至于报错
            header = api_info['baseInfo'].get('header', None)
            # 再进行一步处理--判断 header 不为空
            if header is not None:
                # if isinstance(header, str)    判断header是什么类型?--如果是一个字符串str类型的话 就需要给它做一个解析
                # self.parse_and_replace_variables(header)  调用parse_and_replace_variables方法,把header给传进来
                # else header   若不是字符串,直接给他返回header
                # header =    最后再把结果传回给它自己就行了
                # eval()    调用一个函数 把解析后的数据 self.parse_and_replace_variables(header) 转换为 字典类型
                header = eval(self.parse_and_replace_variables(header)) if isinstance(header, str) else header
                # 打印查看请求头--type() 打印类型
                print(header, type(header))
            # cookies 与 请求头一样
            cookies = api_info['baseInfo'].get('cookies', None)
            if cookies is not None:
                cookies = eval(self.parse_and_replace_variables(cookies)) if isinstance(cookies, str) else cookies
                # 打印查看cookie--type() 打印类型
                print(cookies, type(cookies))
        except Exception as e:
            # 因暂时未写日志模块,所以这里先打印出来
            print(f'出现未知异常:--{e}')


# 调试查看
if __name__ == '__main__':
    # 先引进读取方法 read_yaml--传如一个它的相对路径--赋值给新变量data--[0]通过索引去列表值第一个
    api_info = read_yaml('.././datas/login.yaml')[0]
    # 实例化类 RequestsBase()--并赋值给一个变量对象 req
    req = RequestsBase()

    # 通过这个对象req去调用刚刚封装的方法execute_test_cases--把读取到yaml文件的数据api_info传给它
    req.execute_test_cases(api_info)

16、至此,baseInfo中的数据已经处理完,下篇接着继续处理testCase中的数据;

未完待续。。。

相关推荐
固定资产管理系统软件10 分钟前
商务局RFID固定资产管理系统的应用价值与落地要点解析
大数据·python
tju新生代魔迷35 分钟前
Python学习日记3
学习
lancyu1 小时前
关于多轮对话机器人的上下文Token优化和解决方案
人工智能·python·深度学习·机器学习·chatgpt·机器人·prompt
草莓橙子碗2 小时前
Claude 使用指南
开发语言·python
hsjiasb2 小时前
FreeRTOS学习(三十九)——最终总结
学习·学习笔记·freertos
工具分享2 小时前
陪跑跟品爆单AI选品助手,高效跟品会赔本吗?
人工智能·python
COOLMO研究AI2 小时前
Python 如何实现 AI API 的提示词(Prompt)版本管理与热更新
人工智能·python·prompt
老郑聊AI业财智造2 小时前
Transformer 技术架构与源码分析
人工智能·python·深度学习·语言模型·架构·transformer·软件工程
程序员三藏2 小时前
自动化测试用例编写详解
自动化测试·软件测试·python·功能测试·测试工具·职场和发展·测试用例
m4Rk_3 小时前
【论文阅读】Agent 记忆机制(47):Nemori——用“预测误差”判断什么经验值得被记住
论文阅读·人工智能·学习·开源·github