API 接口自动化测试详细图文教程学习系列25--继续处理testCase中的数据

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

处理testCase中的数据

书接上篇,baseInfo中的数据已经处理完,接着继续处理testCase中的数据,可以看到testCase的最外层是一个list类型,可以通过循环去读取里面的每一组数据;具体详细步骤可自行查看每行代码注释;

pop是一个字典里面的函数,它是一个字典函数;

最外层,处理前相当于一个testCase里面每一个键 完整的地址;但是通过pop获取它的key值名称之后,处理后的数据把字典里面的值给删除了,从字典里面给删除掉,但是,删除之前先会去把 结果给返回出去;然后最后打印时相当于是 删除指定的一个键,并且返回这个值;它跟字典的另一个方法del有区别,del()是删除字典里面一个键,让它不会返回值;pop就是删除之前,它会先把结果给返回出去。

为什么要用这个pop呢?因为最后需要把 testCase 变成只需要获取它的参数类型;

比如说像这个它有很多组数据,像是case_name、validation等等,最后只需要获取 data 参数类型以及它的参数;然后传递给request模块,让它去自动解析参数类型,然后调用对应的一个方法去发起请求;

1、修改 apiutils.py 文件内容,处理testCase里面的数据;

复制代码
# 导包
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))
                
            # 处理testCase里面的数据
            # 通过for循环--循环testcase--通过读取api_info--拿到最外层的数据testCase
            for testcase in api_info['testCase']:
                print('处理前:', testcase)
                # 拿到之后,进行更近一步的处理--通过testcase.pop()去处理--先获取它第一组数据 case_name
                case_name = testcase.pop('case_name')
                # pop()是什么意思?--是字典里面的一个函数
                print('处理后:', testcase)
                
        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)

2、 测试用例名称 case_name 处理完之后,处理 断言validation;有的情况下,断言结果也需要动态的去提取结果;但是在断言里面去写的话,这个值需要使用引号给它引起来;比如说要获取一个登录状态 login_status,直接在配置文件 extract.yaml 中写一个(假如说全局配置文件里面有这个值);有的场景下,需要这种情况,这时候就需要去调用解析,去解析断言的结果;

3、修改 extract.yaml 文件内容,添加"login_status: 登录成功"字段信息;

复制代码
Cookie:
  access_token_cookie: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
token: ba7C07D90Dab6FCAcACc4De8703f2
login_status: 登录成功

4、修改 login.yaml 文件内容,把 testCase 下 validation 里面的 contain 字段对应内容更改为"{'msg':'${get_extract_data(login_status)}'}"数据;

复制代码
- 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: ./datas/login.yaml
       validation:
         - contain: {'msg':'${get_extract_data(login_status)}'}
         - eq: {'msg':'登录成功'}
       extract:
         token: $.token
       extract_list:
         goodid: $.good_id

5、 继续修改 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]

                # 使用正则表达式提取函数名和参数
                # 导入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 []

                    # 使用面向对象反射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:
            # 处理baseInfo里面的数据
            # 获取到接口的服务器地址--通过读取配置文件去获取--通过这个对象去调用
            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
            # 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

            # 处理testCase里面的数据
            # 通过for循环--循环testcase--通过读取api_info--拿到最外层的数据testCase
            for testcase in api_info['testCase']:
                print('处理前:', testcase)
                # 拿到之后,进行更近一步的处理--通过testcase.pop()去处理--先获取它第一组数据 case_name
                case_name = testcase.pop('case_name')
                # 通过变量引用处理断言结果
                # 通过self.parse_and_replace_variables去断言解析的函数--通过for循环中testcase.get获取断言的关键词validation
                # 然后拿到validation后面的这组数据--再把断言的结果值给返回出去并赋值给变量val_result
                val_result = self.parse_and_replace_variables(testcase.get('validation'))
                print(val_result)

        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)

通过这种方式 contain: {'msg':'${get_extract_data(login_status)}'} 去解析的,它是从全局的文件 extract.yaml→login_status 去拿到这个值'登录成功';

6、然后给它替换成 '${get_extract_data(login_status)}'这个;

这里需要注意:如果在断言里面去写这个,因为它是字典类型,所以说这个键和值都需要这种引号方式,跟上面${get_extract_data(Cookie)}是不一样的,上面这个是字符串类型,不用引号去引起来,这种它是字典类型的,键和值都需要引号引起来;如果不写引号,会报错(格式错误);

7、继续修改 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]

                # 使用正则表达式提取函数名和参数
                # 导入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 []

                    # 使用面向对象反射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:
            # 处理baseInfo里面的数据
            # 获取到接口的服务器地址--通过读取配置文件去获取--通过这个对象去调用
            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
            # 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

            # 处理testCase里面的数据
            # 通过for循环--循环testcase--通过读取api_info--拿到最外层的数据testCase
            for testcase in api_info['testCase']:
                print(f'处理前:{testcase}')
                # 拿到之后,进行更近一步的处理--通过testcase.pop()去处理--先获取它第一组数据 case_name
                case_name = testcase.pop('case_name')
                # 通过变量引用处理断言结果
                # 通过self.parse_and_replace_variables去断言解析的函数--通过for循环中testcase.get获取断言的关键词validation
                # 然后拿到validation后面的这组数据--再把断言的结果值给返回出去并赋值给变量val_result
                val_result = self.parse_and_replace_variables(testcase.get('validation'))
                # print(val_result)
                # 拿到断言结果,继续往下写--把testcase['validation']这个值更新成处理后的数据val_result
                testcase['validation'] = val_result
                # 同样是调用pop--然后去获取这个数据variables--最后把结果返回出去给variables
                validation = testcase.pop('validation')
                print(validation, type(validation))
                print(testcase, type(testcase))

        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)

处理前它是一个完整的,testCase全部的数据,把完整的testCase里面给它删掉了,通过这个键把这一组数据给它删掉了,然后处理断言时,也通过pop函数去把断言通过键值对的数据也给他删了,最后再去打印处理后的,只有一个 data,还有extract、extract_list,前面以及删除了的数据就没了;但是删除前先把结果给返回出去了。

8、继续修改 apiutils.py 文件内容,断言已处理,再处理入参,在处理请求参数data之前,先把extract 提取这部分解决;

复制代码
# 导包
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]

                # 使用正则表达式提取函数名和参数
                # 导入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 []

                    # 使用面向对象反射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:
            # 处理baseInfo里面的数据
            # 获取到接口的服务器地址--通过读取配置文件去获取--通过这个对象去调用
            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
            # 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

            # 处理testCase里面的数据
            # 通过for循环--循环testcase--通过读取api_info--拿到最外层的数据testCase
            for testcase in api_info['testCase']:
                print(f'处理前:{testcase}')
                case_name = testcase.pop('case_name')
                val_result = self.parse_and_replace_variables(testcase.get('validation'))
                testcase['validation'] = val_result
                validation = testcase.pop('validation')

                # 处理接口返回值提取部分
                # 通过testcase.pop去删除,删除之前再把结果返回,即返回再删除--获取extract关键词结果
                # --可以在跟一个参数,设置默认值,当login.yaml文件中有extract这个值的时候就返回,没有返回一个None,它就不会报错了
                # extract = testcase.pop('extract', None)
                # print(extract)
                # 处理为 列表的情况 extract_list--要把每一种情况都考虑进去
                # extract_list = testcase.pop('extract_list', None)
                # print(extract_list)

                # 把extract、extract_list 写在一行--看个人习惯,分开写或者一起都行
                extract, extract_list = testcase.pop('extract', None), testcase.pop('extract_list', None)
                print(f'处理后:{testcase}')

        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)

处理前是一个完整的,处理后,把case_name用例名称给删了、validation断言结果给删了、提取值extract、extract_list也通过pop函数给删除了,删掉之后,最后再来查看处理前、处理后数据对比;可以看到 处理前有很多数据,经过一系列操作,处理后它的testcase只变成了一个data入参数据,只保留了入参类型和请求参数,然后可以进一步操作;处理参数类型和请求参数,可以看到 {'data': {'user_name': 'test01', 'passwd': 'admin123'}} 它现在是一个字典,里面有嵌套了一个字典,通过循环去拿到它的类型和参数;

9、修改 extract.yaml 文件,在 配置文件 extract.yaml 中写一个"password: admin123"(假如说全局配置文件里面有这个值);

复制代码
Cookie:
  access_token_cookie: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
token: ba7C07D90Dab6FCAcACc4De8703f2
login_status: 登录成功
password: admin123

10、修改 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: ${get_extract_data(password)}
       files:
         file: ./datas/login.yaml
       validation:
         - contain: {'msg':'${get_extract_data(login_status)}'}
         - eq: {'msg':'登录成功'}
       extract:
         token: $.token
       extract_list:
         goodid: $.good_id

处理参数类型和请求参数;

11、使用 for 循环获取参数类型和参数值,拿到数据之后先判断类型 param_type 在不在这三种其中之一,也就是说参数类型只能写这三种里面的一个,若是在的话则可以解析了,直接去调用解析的方法,把参数值param_value传递给它,最后再把结果返回出去赋值给变量request_params;

复制代码
# 处理参数类型和请求参数
# 使用for循环--参数类型param_type、参数值param_value--它是一个字典直接通过testcase.items() 字典函数
for param_type, param_value in testcase.items():
    # 拿到之后开始进一步操作
    print(f'类型:{param_type}, 参数值:{param_value}')
    # 先判断类型 param_type 在不在这三种其中之一--也就是说参数类型只能写这三种里面的一个
    if param_type in ['params', 'data', 'json']:
        # 若是在的话则可以解析了--直接去调用解析的方法--把参数值param_value传递给它--最后再把结果返回出去赋值给变量request_params
        request_params = self.parse_and_replace_variables(param_value)
        print(request_params)

12、打印一下处理之后的 testcase,可以看到没有用到解析之后的数据;

复制代码
# 处理参数类型和请求参数
# 使用for循环--参数类型param_type、参数值param_value--它是一个字典直接通过testcase.items() 字典函数
for param_type, param_value in testcase.items():
    # 拿到之后开始进一步操作
    print(f'类型:{param_type}, 参数值:{param_value}')
    # 先判断类型 param_type 在不在这三种其中之一--也就是说参数类型只能写这三种里面的一个
    if param_type in ['params', 'data', 'json']:
        # 若是在的话则可以解析了--直接去调用解析的方法--把参数值param_value传递给它--最后再把结果返回出去赋值给变量request_params
        request_params = self.parse_and_replace_variables(param_value)
        print(request_params)

print('处理之后:', testcase)

13、再加一步处理,把testcase字典key值 参数类型更新为 通过解析之后的数据 request_params,可以查看到结果是解析之后的数据;

复制代码
# 导包
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]

                # 使用正则表达式提取函数名和参数
                # 导入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 []

                    # 使用面向对象反射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:
            # 处理baseInfo里面的数据
            # 获取到接口的服务器地址--通过读取配置文件去获取--通过这个对象去调用
            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
            # 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

            # 处理testCase里面的数据
            # 通过for循环--循环testcase--通过读取api_info--拿到最外层的数据testCase
            for testcase in api_info['testCase']:
                print(f'处理前:{testcase}')
                case_name = testcase.pop('case_name')
                val_result = self.parse_and_replace_variables(testcase.get('validation'))
                testcase['validation'] = val_result
                validation = testcase.pop('validation')

                # 处理接口返回值提取部分
                # 通过testcase.pop去删除,删除之前再把结果返回,即返回再删除--获取extract关键词结果
                # --可以在跟一个参数,设置默认值,当login.yaml文件中有extract这个值的时候就返回,没有返回一个None,它就不会报错了
                # extract = testcase.pop('extract', None)
                # print(extract)
                # 处理为 列表的情况 extract_list--要把每一种情况都考虑进去
                # extract_list = testcase.pop('extract_list', None)
                # print(extract_list)

                # 把extract、extract_list 写在一行--看个人习惯,分开写或者一起都行
                extract, extract_list = testcase.pop('extract', None), testcase.pop('extract_list', None)
                print(f'处理后:{testcase}')

                # 处理参数类型和请求参数
                # 使用for循环--参数类型param_type、参数值param_value--它是一个字典直接通过testcase.items() 字典函数
                for param_type, param_value in testcase.items():
                    # 拿到之后开始进一步操作
                    print(f'类型:{param_type}, 参数值:{param_value}')
                    # 先判断类型 param_type 在不在这三种其中之一--也就是说参数类型只能写这三种里面的一个
                    if param_type in ['params', 'data', 'json']:
                        # 若是在的话则可以解析了--直接去调用解析的方法--把参数值param_value传递给它--最后再把结果返回出去赋值给变量request_params
                        request_params = self.parse_and_replace_variables(param_value)
                        print(request_params)
                        # 把testcase字典key值 参数类型更新为 通过解析之后的数据 request_params
                        testcase[param_type] = request_params

                print('处理之后:', testcase)

        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)

数据处理完成之后,下一步可以发起结果请求了;

发起请求结果

14、继续修改 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
# 引入 发起请求模块
from unit_tools.sendrequests import SendRequests
# 创建一个类 RequestsBase
class RequestsBase:

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

    # 创建一个方法 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]

                # 使用正则表达式提取函数名和参数
                # 导入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 []

                    # 使用面向对象反射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:
            # 处理baseInfo里面的数据
            # 获取到接口的服务器地址--通过读取配置文件去获取--通过这个对象去调用
            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方法获取看里面有没有请求头--如果有的话就返回给变量headers,若没有,则传个默认空值None就行
            # 判断请求头是否可选,使用get的话,当headers有的话就返回,没有时就返回None,不至于报错
            headers = api_info['baseInfo'].get('headers', None)
            # 再进行一步处理--判断 headers 不为空
            if headers is not None:
                # if isinstance(headers, str)    判断headers是什么类型?--如果是一个字符串str类型的话 就需要给它做一个解析
                # self.parse_and_replace_variables(headers)  调用parse_and_replace_variables方法,把headers给传进来
                # else headers   若不是字符串,直接给他返回headers
                # headers =    最后再把结果传回给它自己就行了
                # eval()    调用一个函数 把解析后的数据 self.parse_and_replace_variables(headers) 转换为 字典类型
                headers = eval(self.parse_and_replace_variables(headers)) if isinstance(headers, str) else headers
                # print(headers)

            # 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
                # print(cookies)

            # 处理testCase里面的数据
            # 通过for循环--循环testcase--通过读取api_info--拿到最外层的数据testCase
            for testcase in api_info['testCase']:
                print(f'处理前:{testcase}')
                # 拿到之后,进行更近一步的处理--通过testcase.pop()去处理--先获取它第一组数据 case_name
                case_name = testcase.pop('case_name')
                # 通过变量引用处理断言结果
                # 通过self.parse_and_replace_variables去断言解析的函数--通过for循环中testcase.get获取断言的关键词validation
                # 然后拿到validation后面的这组数据--再把断言的结果值给返回出去并赋值给变量val_result
                val_result = self.parse_and_replace_variables(testcase.get('validation'))
                # print(val_result)
                # 拿到断言结果,继续往下写--把testcase['validation']这个值更新成处理后的数据val_result
                testcase['validation'] = val_result
                # 同样是调用pop--然后去获取这个数据variables--最后把结果返回出去给variables
                validation = testcase.pop('validation')
                # print(validation, type(validation))
                # print(testcase, type(testcase))

                # 处理接口返回值提取部分
                # 通过testcase.pop去删除,删除之前再把结果返回,即返回再删除--获取extract关键词结果
                # --可以在跟一个参数,设置默认值,当login.yaml文件中有extract这个值的时候就返回,没有返回一个None,它就不会报错了
                # extract = testcase.pop('extract', None)
                # print(extract)
                # 处理为 列表的情况 extract_list--要把每一种情况都考虑进去
                # extract_list = testcase.pop('extract_list', None)
                # print(extract_list)
                # 把extract、extract_list 写在一行--看个人习惯,分开写或者一起都行
                extract, extract_list = testcase.pop('extract', None), testcase.pop('extract_list', None)
                print(f'处理后:{testcase}')

                # 处理参数类型和请求参数
                # 使用for循环--参数类型param_type、参数值param_value--它是一个字典直接通过testcase.items() 字典函数
                for param_type, param_value in testcase.items():
                    # 拿到之后开始进一步操作
                    # print(f'类型:{param_type}, 参数值:{param_value}')
                    # 先判断类型 param_type 在不在这三种其中之一--也就是说参数类型只能写这三种里面的一个
                    if param_type in ['params', 'data', 'json']:
                        # 若是在的话则可以解析了--直接去调用解析的方法--把参数值param_value传递给它--最后再把结果返回出去赋值给变量request_params
                        request_params = self.parse_and_replace_variables(param_value)
                        # print(request_params)
                        # 把testcase字典key值 参数类型更新为 通过解析之后的数据 request_params
                        testcase[param_type] = request_params

                print('处理之后:', testcase)

        except Exception as e:
            # 因暂时未写日志模块,所以这里先打印出来
            print(f'出现未知异常:--{e}')


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

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

因为request底层源码里面的关键字单词 headers、cookies、files 后面都带有"s"的,而封装的方法 execute_api_request 是没有带"s"的,所以为了不理解错误,这里可以特地加上"s"(可以自行添加,主要是 apiutils.pysendrequests.py 和 yaml 文件,修改自己定义的方法中相关的"headers、cookies、files",若自己能分清的话,亦可不用更改);

15、如若测试项目需要传 headers、cookies 字段信息,建议进行动态解析方式;

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

16、修改 login.yaml 文件,而本次接口服务测试下面不需要传 headers、cookies 字段信息,所以可删除,上面两个假设的也需要还原回去;

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

17、修改 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
# 引入 发起请求模块
from unit_tools.sendrequests import SendRequests
# 创建一个类 RequestsBase
class RequestsBase:

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

    # 创建一个方法 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]

                # 使用正则表达式提取函数名和参数
                # 导入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 []

                    # 使用面向对象反射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:
            # 处理baseInfo里面的数据
            # 获取到接口的服务器地址--通过读取配置文件去获取--通过这个对象去调用
            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方法获取看里面有没有请求头--如果有的话就返回给变量headers,若没有,则传个默认空值None就行
            # 判断请求头是否可选,使用get的话,当headers有的话就返回,没有时就返回None,不至于报错
            headers = api_info['baseInfo'].get('headers', None)
            # 再进行一步处理--判断 headers 不为空
            if headers is not None:
                # if isinstance(headers, str)    判断headers是什么类型?--如果是一个字符串str类型的话 就需要给它做一个解析
                # self.parse_and_replace_variables(headers)  调用parse_and_replace_variables方法,把headers给传进来
                # else headers   若不是字符串,直接给他返回headers
                # headers =    最后再把结果传回给它自己就行了
                # eval()    调用一个函数 把解析后的数据 self.parse_and_replace_variables(headers) 转换为 字典类型
                headers = eval(self.parse_and_replace_variables(headers)) if isinstance(headers, str) else headers
                # print(headers)

            # 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
                # print(cookies)

            # 处理testCase里面的数据
            # 通过for循环--循环testcase--通过读取api_info--拿到最外层的数据testCase
            for testcase in api_info['testCase']:
                print(f'处理前:{testcase}')
                # 拿到之后,进行更近一步的处理--通过testcase.pop()去处理--先获取它第一组数据 case_name
                case_name = testcase.pop('case_name')
                # 通过变量引用处理断言结果
                # 通过self.parse_and_replace_variables去断言解析的函数--通过for循环中testcase.get获取断言的关键词validation
                # 然后拿到validation后面的这组数据--再把断言的结果值给返回出去并赋值给变量val_result
                val_result = self.parse_and_replace_variables(testcase.get('validation'))
                # print(val_result)
                # 拿到断言结果,继续往下写--把testcase['validation']这个值更新成处理后的数据val_result
                testcase['validation'] = val_result
                # 同样是调用pop--然后去获取这个数据variables--最后把结果返回出去给variables
                validation = testcase.pop('validation')
                # print(validation, type(validation))
                # print(testcase, type(testcase))

                # 处理接口返回值提取部分
                # 通过testcase.pop去删除,删除之前再把结果返回,即返回再删除--获取extract关键词结果
                # --可以在跟一个参数,设置默认值,当login.yaml文件中有extract这个值的时候就返回,没有返回一个None,它就不会报错了
                # extract = testcase.pop('extract', None)
                # print(extract)
                # 处理为 列表的情况 extract_list--要把每一种情况都考虑进去
                # extract_list = testcase.pop('extract_list', None)
                # print(extract_list)
                # 把extract、extract_list 写在一行--看个人习惯,分开写或者一起都行
                extract, extract_list = testcase.pop('extract', None), testcase.pop('extract_list', None)
                print(f'处理后:{testcase}')

                # 处理参数类型和请求参数
                # 使用for循环--参数类型param_type、参数值param_value--它是一个字典直接通过testcase.items() 字典函数
                for param_type, param_value in testcase.items():
                    # 拿到之后开始进一步操作
                    # print(f'类型:{param_type}, 参数值:{param_value}')
                    # 先判断类型 param_type 在不在这三种其中之一--也就是说参数类型只能写这三种里面的一个
                    if param_type in ['params', 'data', 'json']:
                        # 若是在的话则可以解析了--直接去调用解析的方法--把参数值param_value传递给它--最后再把结果返回出去赋值给变量request_params
                        request_params = self.parse_and_replace_variables(param_value)
                        # print(request_params)
                        # 把testcase字典key值 参数类型更新为 通过解析之后的数据 request_params
                        testcase[param_type] = request_params
                print('处理之后:', testcase)
                # 文件上传现在还没有,暂时定义个控制传给参数
                files =None
                # 通过刚刚创建的实例化对象self.send_request去调用执行接口请求的方法execute_api_request()--里面的参数需要一一对应
                # **kwargs 未知数量传参------------直接把 **testcase 传给它
                response = self.send_request.execute_api_request(api_name=api_name, url=url, method=method, headers=headers, case_name=case_name, cookies=cookies, files=files, **testcase)
                status_code = response.status_code
                print(status_code)

        except Exception as e:
            # 因暂时未写日志模块,所以这里先打印出来
            print(f'出现未知异常:--{e}')

# 调试查看
if __name__ == '__main__':
    # 先引进读取方法 read_yaml--传如一个它的相对路径--赋值给新变量data--[0]通过索引去列表值第一个
    api_info = read_yaml('.././data/login.yaml')[0]
    # 实例化类 RequestsBase()--并赋值给一个变量对象 req
    req = RequestsBase()
    # 通过类对象去调用parse_and_replace_variables(data) 这个解析的方法--把读取到的 data 这个yaml数据传给它
    # res = req.parse_and_replace_variables(data)
    # print(f'解析后:{res}')
    # 通过这个对象req去调用刚刚封装的方法execute_test_cases--把读取到yaml文件的数据api_info传给它
    req.execute_test_cases(api_info)

18、修改 login.yaml 文件;

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

19、清空 extract.yaml全局配置文件;

20、修改 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
# 引入 发起请求模块
from unit_tools.sendrequests import SendRequests
# 创建一个类 RequestsBase
class RequestsBase:

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

    # 创建一个方法 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]

                # 使用正则表达式提取函数名和参数
                # 导入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 []

                    # 使用面向对象反射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:
            # 处理baseInfo里面的数据
            # 获取到接口的服务器地址--通过读取配置文件去获取--通过这个对象去调用
            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方法获取看里面有没有请求头--如果有的话就返回给变量headers,若没有,则传个默认空值None就行
            # 判断请求头是否可选,使用get的话,当headers有的话就返回,没有时就返回None,不至于报错
            headers = api_info['baseInfo'].get('headers', None)
            # 再进行一步处理--判断 headers 不为空
            if headers is not None:
                # if isinstance(headers, str)    判断headers是什么类型?--如果是一个字符串str类型的话 就需要给它做一个解析
                # self.parse_and_replace_variables(headers)  调用parse_and_replace_variables方法,把headers给传进来
                # else headers   若不是字符串,直接给他返回headers
                # headers =    最后再把结果传回给它自己就行了
                # eval()    调用一个函数 把解析后的数据 self.parse_and_replace_variables(headers) 转换为 字典类型
                headers = eval(self.parse_and_replace_variables(headers)) if isinstance(headers, str) else headers
                # print(headers)

            # 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
                # print(cookies)

            # 处理testCase里面的数据
            # 通过for循环--循环testcase--通过读取api_info--拿到最外层的数据testCase
            for testcase in api_info['testCase']:
                print(f'处理前:{testcase}')
                # 拿到之后,进行更近一步的处理--通过testcase.pop()去处理--先获取它第一组数据 case_name
                case_name = testcase.pop('case_name')
                # 通过变量引用处理断言结果
                # 通过self.parse_and_replace_variables去断言解析的函数--通过for循环中testcase.get获取断言的关键词validation
                # 然后拿到validation后面的这组数据--再把断言的结果值给返回出去并赋值给变量val_result
                val_result = self.parse_and_replace_variables(testcase.get('validation'))
                # print(val_result)
                # 拿到断言结果,继续往下写--把testcase['validation']这个值更新成处理后的数据val_result
                testcase['validation'] = val_result
                # 同样是调用pop--然后去获取这个数据variables--最后把结果返回出去给variables
                validation = testcase.pop('validation')
                # print(validation, type(validation))
                # print(testcase, type(testcase))

                # 处理接口返回值提取部分
                # 通过testcase.pop去删除,删除之前再把结果返回,即返回再删除--获取extract关键词结果
                # --可以在跟一个参数,设置默认值,当login.yaml文件中有extract这个值的时候就返回,没有返回一个None,它就不会报错了
                # extract = testcase.pop('extract', None)
                # print(extract)
                # 处理为 列表的情况 extract_list--要把每一种情况都考虑进去
                # extract_list = testcase.pop('extract_list', None)
                # print(extract_list)
                # 把extract、extract_list 写在一行--看个人习惯,分开写或者一起都行
                extract, extract_list = testcase.pop('extract', None), testcase.pop('extract_list', None)
                print(f'处理后:{testcase}')

                # 处理参数类型和请求参数
                # 使用for循环--参数类型param_type、参数值param_value--它是一个字典直接通过testcase.items() 字典函数
                for param_type, param_value in testcase.items():
                    # 拿到之后开始进一步操作
                    # print(f'类型:{param_type}, 参数值:{param_value}')
                    # 先判断类型 param_type 在不在这三种其中之一--也就是说参数类型只能写这三种里面的一个
                    if param_type in ['params', 'data', 'json']:
                        # 若是在的话则可以解析了--直接去调用解析的方法--把参数值param_value传递给它--最后再把结果返回出去赋值给变量request_params
                        request_params = self.parse_and_replace_variables(param_value)
                        # print(request_params)
                        # 把testcase字典key值 参数类型更新为 通过解析之后的数据 request_params
                        testcase[param_type] = request_params
                print('处理之后:', testcase)
                # 文件上传现在还没有,暂时定义个控制传给参数
                files =None
                # 通过刚刚创建的实例化对象self.send_request去调用执行接口请求的方法execute_api_request()--里面的参数需要一一对应
                # **kwargs 未知数量传参------------直接把 **testcase 传给它
                response = self.send_request.execute_api_request(api_name=api_name, url=url, method=method, header=headers, case_name=case_name, cookie=cookies, file=files, **testcase)
                status_code = response.status_code
                print(status_code)

        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()
    # 通过类对象去调用parse_and_replace_variables(data) 这个解析的方法--把读取到的 data 这个yaml数据传给它
    # res = req.parse_and_replace_variables(data)
    # print(f'解析后:{res}')
    # 通过这个对象req去调用刚刚封装的方法execute_test_cases--把读取到yaml文件的数据api_info传给它
    req.execute_test_cases(api_info)

总结

首先这个方法execute_test_cases用于处理 yaml 文件里面的接口信息,然后还有多种情况需要处理(如yaml文件中就没有headers时的处理,默认返回None,如果有的话,又分两种情况,使用引用的情况,变量引用的方式就去调用变量解析方法,如果是固定写死的,就直接返回headers),然后cookies也是,可选可不选;处理完baseInfo中的数据后,继续处理testCase中的,然后分别把里面的多组数据全部给它删除之前把它的结果返回出去,最后得到一个字典,只有data这一组数据,data数据类型 和data后面跟上data的参数,这时候可以通过 **testcase ,把最后处理过的数据通过 **testcase 传递过去,这个参数 **testcase 它会传到调用 execute_api_request 方法中参数(**kwargs),最终会调用request模块(三种情况处理get→params、post→data/json;如果yaml文件里面写的是json它就会调用json传递的参数,如果yaml文件里面是get请求,就会调用params,会根据yaml文件里面的这个参数类型去动态的调用request模块里面哪一个参数去执行接口请求;

yaml文件里面只能选'params','data','json' 这三种类型其中之一,get的话就选 params,如果是post请求的话,根据请求头,如果是表单提交的话,就填data,如果表单里面约定的是json提交的话,就需要写一个json;最后去传递request模块调用一个相应的入参格式去请求,这里都处理完之后,把之前封装的 发起接口请求这个方法 给它传过来之后,把上面获取到的yaml数据传递给它,最终会执行接口请求,把结果返回出去;至此,已经实现了处理接口信息,发起接口请求;还余下把结果提取值给写入到指定文件,然后还有断言、断言结果。

未完待续。。。

相关推荐
Wang ruoxi2 小时前
Pygame 小游戏——节奏点击
python·pygame
你的保护色2 小时前
数据库第一章-基础知识学习
数据库·学习
夕除2 小时前
AOP 实现 Redis 缓存切面解析
java·开发语言·python
fanjiu20202 小时前
python查询nightingale监控
python
数智工坊2 小时前
【ROS 2 全栈入门指南一】:从本质认知到环境搭建与核心原理解析
学习·机器人
TechWayfarer2 小时前
IP画像在企业安全中的应用:它能做什么?不能替代什么
网络·python·tcp/ip·安全·网络安全
大大杰哥2 小时前
Vue2学习(1)--了解基本方法与概念
javascript·学习·vue
Rauser Mack2 小时前
不懂编程,但是vibe coding一个扫雷游戏
人工智能·python·游戏·html·prompt
nashane2 小时前
HarmonyOS 6商城开发学习:消息中心未读清零——@ObservedV2+@Trace驱动一键清除
学习·华为·harmonyos