• 2022/9/5开始第三版接口自动化(yaml用例)测试框架(记录搭建过程)


    序章

    第一代框架

    源码地址
    教程
    在这里插入图片描述

    第二代框架

    源码地址
    搭建教程
    使用教程
    在这里插入图片描述

    一、yaml用例设计

    这个模板不代表最终模板,后续可能还有改动

    #用例(名称)标题,需要参数依赖的全部设置字符格式,后期解析成字典列表等格式
    用例标题:
      #接口地址,也支持参数依赖
      path: /test/$.id
      #请求方法
      method: post
      #有值就用这个,没用就使用默认配置的,有header里面使用参数依赖的场景
      header: {}
      # 是否运行
      is_run: True
      # 前置sql:sql有2大类,一个是查询有返回值,一个是增删改无返回值,前置sql为无返回值类型
      precondition_sql:
        - UPDATE case_test SET title = '标题2', ex = '44' WHERE id = 2
        - UPDATE case_test SET title = '标题3', ex = '55' WHERE id = 3
      #请求参数较多,这里就使用原始字典格式,除了提取表达式,其他的都带上引号,预防出错,random_time()随机函数使用
      data:
        {
          "id":$.id,
          "projectNo": "320SF000206004",
          "name":$.name,
          "time": random_time(),
          "str": random_str(6),
          "int": random_number(5),
          # 请求参数sql为有返回值查询
          "sql": sql-SELECT title FROM case_test where id=2
        }
      #参数类型 json 或者 form表单
      data_type: json
      #从接口返回结果提取哪些字段和提取表达式,比如从返回数据提取用户id和name
      extract_key:
        id: $.id
        name: $.name
      #断言表达式
      # 除了数字类型比较,其他的字符或者表达式全部带上引号,表达式必须要写在后面(必须按照格式来)
      # 断言sql为有返回值查询
      assert_expression:
        #判断响应码是否和预期一致
        code: 200
        #判断预期值是否在返回值里面(用值是否在接口返回的内容里面判断)
        body: 内容包含
        #判断json提取值是否和预期一致(用键和值比较等于,大于小于或者in判断)
        json:
          {"ig" : ">$.ig","rng" : "==$.rng","edg" : "<$.edg",'1':'in$.ig'}
        #判断sql查询值是否和预期一致(用键和值作判断,规则和上面一致)
        sql:
          {'1':">sql-SELECT title FROM case_test where id=2",'2':"==sql-SELECT title FROM case_test where id=2"}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    二、配置哪些字段需要字符化

    ‘headers’,‘data’,'assert_expression’这三个字段是需要参数依赖的,要让它们被读取处理时先转成字符格式,方便后续使用.replace()替换方法,替换完后使用eval()方法去除字符恢复原来的格式
    在这里插入图片描述

    三、用例读出

    yaml读出方法基本和第二版无改动
    文件目录排除做了一些修改,增加了一个raw_case_path用例所属文件夹的配置
    用例读取的方法进行了重构
    增加了把用例的某些字段字符化的处理,用例的格式也变成了列表嵌套字典的格式

    在这里插入图片描述

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    '''
    @time    : 2022/9/5 
    @Author  : LL
    @File    : read_file.py
    '''
    import os
    from pathlib import Path
    from config.config import exclude_file,exclude_dir,raw_case_path,str_zd
    import yaml
    
    
    class ReadFile:
        # D:\python2022\yaml_pytest_drf_vue/
        project_directory = str(Path(__file__).parent.parent) + '/'
    
        @classmethod
        def read_yaml(cls, path):
            '''读取yaml文件,以字典格式返回{'用例标题':{'path':'/test','data':{'id':1}}}'''
            try:
                path = cls.project_directory + path
                file = open(path, 'r', encoding='utf-8')
                with file as doc:
                    content = yaml.load(doc, Loader=yaml.Loader)
                    return content
            except Exception as e:
                # logger.error(f'读取{path}文件出错:{e},检查文件内容格式是否错误')
                print(f'读取{path}文件出错:{e},检查文件内容格式是否错误')
    
        @classmethod
        def file_execute_list(cls, exclude_file=exclude_file, exclude_dir=exclude_dir,raw_case_path=raw_case_path):
            '''
            :param exclude_dir: 要排除的目录(二级目录)
            :param exclude_file: 要排除的文件(case目录下所有文件)
            :param raw_case_path: 用例原始文件总目录
            :return: 获取case下的所有用例文件列表,最多支持二级目录,通用排除文件返回最终要执行的用例文件
            '''
            file_list = []
            case_path = cls.project_directory + raw_case_path
            # case目录下的所有文件
            for filename in os.listdir(case_path):
                if 'yaml' in filename:
                    # 要储存为case开头的目录,方便读取用例使用
                    file_list.append(raw_case_path + filename)
                else:
                    # 遍历case下面的二级目录
                    for i in os.listdir(case_path + '/' + filename):
                        # 检查这个二级目录是否需要被排除
                        if filename in exclude_dir:
                            continue
                        # 要储存为case开头的目录,方便读取用例使用,这是二级目录得把二级目录拼接上
                        file_list.append(raw_case_path + filename + '/' + i)
            # 文件列表不为空的话一个一个的排除掉这些文件
            if exclude_file != []:
                for i in exclude_file:
                    file_list.remove(i)
            return file_list
    
        @classmethod
        def read_case(cls):
            '''最终返回列表嵌套字段的用例格式'''
            #读取用例要执行的列表
            path_list = cls.file_execute_list()
            case_list=[]
            for i in path_list:
                #i就是单yaml文件路径,然后读出来放到用例列表里面
                case=cls.read_yaml(i)
                # print(case)
                for k,v in case.items():
                    #由于yaml是以用例标题为键,直接赋予名字,然后把标题加到主体里面去
                    case_title=k
                    case_body=v
                    #这里面有需要字符化的字段,字典,列表等全部转成字符串
                    # data_d = type(case_body['data'])
                    # print(f'测试下数据是不是转化成功-转换后类型{data_d}')
                    for str_z in str_zd:
                        try:
                            case_body[str_z]=str(case_body[str_z])
                        except KeyError as e:
                            print(f'用例无字段{e}')
                    case_body.update({'case_title':case_title})
                    # data_s=type(case_body['data'])
                    # print(f'测试下数据是不是转化成功-转换后类型{data_s}')
                    #判断用例是否需要运行
                    if case_body['is_run']==True:
                        #最后把case_body加入到case_list里面
                        case_list.append(case_body)
            return case_list
    
    
    
    if __name__ == '__main__':
        case = ReadFile.read_yaml('raw_case_file/test.yaml')
        # print(type(str(case['用例标题']['data'])))
        # print((str(case['用例标题']['data'])))
        print(ReadFile.file_execute_list())
        ReadFile.read_case()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98

    四、requests二次封装

    对比第二版的requests二次封装进行了重构,添加支持get请求传参,支持form表格和json传参,增加put和delete方法
    在这里插入图片描述

    五、pytest基本使用配置

    pytest运行命令,详细展示运行详情,生成报告数据到./report/data目录

    addopts = -vs --alluredir ./report/data
    
    • 1

    设置要运行用例文件的路径,我这里就一个文件,指定个目录就行

    testpaths = case_run
    
    • 1

    在这里插入图片描述
    简单的一个主方法,可以看到控制台打印出了三条用例数据,把从yaml文件里面读取的用例使用parametrize参数化
    在这里插入图片描述

    六、参数提取和替换

    参数替换:相比于第二代重构了这个方法,替换是先把要替换的数据字符化,然后通过正则查找到需要替换的数据,然后去参数池里面取到,替换到原数据中,最后解除字符化,变为原来的格式
    参数提取:把提取表达式转化为具体的值,把提取的值保存到参数池里面

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    '''
    参数替换使用正则表达式,参数池格式为${},函数格式为f{},提取使用jsonpath
    '''
    from jsonpath import jsonpath
    import re
    
    class ParameterSetting:
        # 参数池
        access_value = {}
    
        @classmethod
        def data_is_replace(cls, data):
            '''
            :param data: 请求参数data和提取参数extract_key
            :return: 请求参数是否需要被替换
            '''
            if data is None:
                return False
            for k, v in data.items():
                if '&{' in v or 'f{' in v:
                    return True
            return False
    
        # 基于jsonpath表达式提取
        @classmethod
        def extract(cls, api_response: dict, extract_key: dict):
            '''
            :param api_response: 接口返回值 例:{'code':200,'content':{'bill':'ys123456'}}
            :param extract_key:  提取表达式 例:{'bill': '$.content.bill'}
            '''
    
            extract_value = {}
            # 把提取表达式转化为具体的值 例:{'bill': 'ys123456'}
            for k, v in extract_key.items():
                extract_value[k] = jsonpath(api_response, v)[0]
            # 把提取的值保存到参数池里面
            for k, v in extract_value.items():
                cls.access_value[k] = v
    
        # 基于正则表达式替换
        @classmethod
        def replaces(cls, data: str):
            #这里${}是要从参数池里面读取的并且替换的数据
            replace_json_list = re.findall('\${(.*?)}', data)
            replace_f_list = re.findall('\f{(.*?)}', data)
            if len(replace_json_list)>0:
                #把要替换的值变成jsonpath表达式,从参数池里面读取,然后替换
                for i in replace_json_list:
                    #i就是${}里面的值,'$.'+i把它拼接成jsonpath表达式
                    print(i)
                    #jsonpath(cls.access_value,'$.'+i)[0]就是从参数池里面取出的值
                    print(jsonpath(cls.access_value,'$.'+i)[0])
                    #f'${"{"+i+"}"}'就是要被替换的值 例${cc},这里有语法冲突,我使用字符拼接了
                    data=data.replace(f'${"{"+i+"}"}',jsonpath(cls.access_value,'$.'+i)[0])
            return eval(data)
    
    
    
    #方法测试
    if __name__ == '__main__':
        print(f'提取前参数池{ParameterSetting.access_value}')
        api_response={'code':200,'content':{'bill':'ys123456','aa':'aa的值'},'bb':'bb的值','cc':'cc的值'}
        extract_key={'bill': '$.content.bill','bb':'$.bb','cc':'$.cc','aa':'$.content.aa'}
        ParameterSetting.extract(api_response,extract_key)
        print(f'提取后参数池{ParameterSetting.access_value}')
        data={
            's':'${aa}',
            'v':['${bb}','${cc}']
        }
        print(ParameterSetting.replaces(str(data)))
        print(type(ParameterSetting.replaces(str(data))))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    测试
    在这里插入图片描述

    问题:yaml格式错误

    修改用例模板,这个格式会报错
    在这里插入图片描述
    解决方案直接加引号,直接在这里就把他字符化,不用外面再去字符化了,headers和assert_expression里面的json也这样处理,注意了,里面的引号就得是双引号了

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    那之前设置的字符化配置就失去意义了,先不动,有影响在改

    七、新的用例模板

    #用例(名称)标题,需要参数依赖的全部设置字符格式,后期解析成字典列表等格式
    用例标题0:
      #接口地址,也支持参数依赖
      path: /test/{id}
      #请求方法
      method: post
      #有值就用这个,没用就使用默认配置的,有header里面使用参数依赖的场景
      headers: '{"token":"${token}"}'
      # 是否运行
      is_run: True
      # 前置sql:sql有2大类,一个是查询有返回值,一个是增删改无返回值,前置sql为无返回值类型
      #  precondition_sql:
      #    - UPDATE case_test SET title = '标题2', ex = '44' WHERE id = 2
      #    - UPDATE case_test SET title = '标题3', ex = '55' WHERE id = 3
      #请求参数较多,这里就使用原始字典格式,除了提取表达式,其他的都带上引号,预防出错,random_time()随机函数使用
      data:
        '
        {
              "id":${id},
              "projectNo": "320SF000206004",
              "name": ${name},
              "time": random_time(),
              "str": random_str(6),
              "int": random_number(5),
              # 请求参数sql为有返回值查询
              "sql": sql-SELECT title FROM case_test where id=2
            }
        '
      #参数类型 json 或者 form表单
      data_type: json
      #从接口返回结果提取哪些字段和提取表达式,比如从返回数据提取用户id和name
      extract_key:
        id: $.id
        name: $.name
      #断言表达式
      # 除了数字类型比较,其他的字符或者表达式全部带上引号,表达式必须要写在后面(必须按照格式来)
      # 断言sql为有返回值查询
      assert_expression:
        #判断响应码是否和预期一致
        code: 200
        #判断预期值是否在返回值里面(用值是否在接口返回的内容里面判断)
        body: 内容包含
        #判断json提取值是否和预期一致(用键和值比较等于,大于小于或者in判断)
        json:
          '{"ig": ${name},"rng": ${name},"edg": "${name}"}'
        #判断sql查询值是否和预期一致(用键和值作判断,规则和上面一致)
    #    sql:
    #      {'1':">sql-SELECT title FROM case_test where id=2",'2':"==sql-SELECT title FROM case_test where id=2"}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    八、断言处理

    和第二版相比除了类名其他全部不同
    通过接口返回先获取到响应码,响应json信息,响应文本信息
    断言code判断是否相等就行,断言body判断内容是否在返回文本里面就行
    在这里插入图片描述
    这里要修改下之前的requests方法,之前这个都是固定返回json,现在返回整个对象信息,使用时要自行携带token
    在这里插入图片描述
    json断言处理
    这里也需要用到之前的正则提取,之前参数替换是用正则在参数池里面找数据,在断言这里就是在接口的返回参数里面找数据
    我把方法优化了一下,把通用的地方提取了出来
    在这里插入图片描述
    之前的方法改成了这样
    在这里插入图片描述
    json断言写完,等待测试
    在这里插入图片描述

    九、组装请求,参数替换和断言

    导包

    启动下mock接口
    在这里插入图片描述
    在请求时想到,如果不需要依赖的参数在读出的headers和data等信息是字符格式的,这样请求requests就会报错 增加下字符转化,以防万一,这个url组成先改掉,后续加上配置后在改回来
    在这里插入图片描述
    组装完成,感觉会有很多报错
    在这里插入图片描述

    十、开始运行和排坑(重要,内容很多)

    以前只在这里判断了的请求参数是否需要依赖,我现在是判断所有的用例数据是否需要依赖
    在这里插入图片描述
    直接重构,使用字符格式判断
    在这里插入图片描述
    看请求信息
    在这里插入图片描述

    问题: 参数位置颠倒

    到这来来发现位置被调换了,data和headers的信息
    在这里插入图片描述

    我把data调整到headers前面再测试
    在这里插入图片描述
    每填就是None,判断这里改下,返回成功
    在这里插入图片描述
    在这里插入图片描述

    问题: 提前使用json()方法

    这里报错,因为我提前使用了json()方法,导致没有status_code属性而报错
    在这里插入图片描述
    删除下,把断点换个地方
    在这里插入图片描述
    可以看到这个对象的三个属性值
    在这里插入图片描述

    问题: 无效的转义

    无效的转义,之前单独测试没啥问题,现在加个r原生字符re.findall(r’${(.*?)}', data)就好了
    在这里插入图片描述
    这里开始三个字段进行了字符化处理,导致这里字符格式使用了列表格式的索引方法,把之前那个断言字段的字符化去掉
    在这里插入图片描述
    json断言信息就传进来了
    在这里插入图片描述
    正则未提取到值
    在这里插入图片描述

    问题: 用例格式写错

    格式写错了
    在这里插入图片描述
    现在就有了

    在这里插入图片描述
    这里k和v是不等的,断言就失败了
    在这里插入图片描述
    修改下用例,让断言通过
    在这里插入图片描述
    get不带参数的用例运行通过,但是还没有提取参数
    在这里插入图片描述

    问题: 之前的判断是否需要提取方法无法使用

    写一个判断是否需要提取的方法,之前那个不能用了
    在这里插入图片描述
    使用和测试
    在这里插入图片描述

    问题:替换判断字符写错

    使用post请求,参数替换,这里忘记改了,导致参数没有替换在这里插入图片描述
    参数替换把断言里面的也给替换了,需要区分一下,断言的参数是在接口返回里面拿的,我直接在这里先把断言部分数据删除掉
    在这里插入图片描述
    是否需要替换参数也需要改下
    在这里插入图片描述

    这里要接一下,不然参数就没有替换成功了
    在这里插入图片描述

    问题:requests封装有误

    这里发现我虽然在用例里面写的post,但是还是用的get,这个方法这样写有问题,得改下
    在这里插入图片描述
    修改后

    在这里插入图片描述

    十一、请求基本信息配置

    对于用例里面的header信息采用追加的方式加入到这里,然后发起请求

    在这里插入图片描述
    信息全读出来
    在这里插入图片描述
    增加一个配置信息,配置当前测试的环境
    在这里插入图片描述
    在这里插入图片描述
    最后把信息返回出去
    在这里插入图片描述
    在requests里面调用和处理
    在这里插入图片描述

  • 相关阅读:
    逆向-beginners之数组溢出
    【树莓派不吃灰】命令篇② 常用文件管理命令
    抑制细胞代谢紊乱的抑制剂
    mac安装Nginx&Nginx常见的命令&Nginx反向代理、负载均衡
    PoseiSwap 参赛,参与斯坦福、Nautilus等联合主办的 Hackathon 活动
    前端常见面试题:post请求发送几次?
    20.项目开发之量化交易QuantTrade(二)
    java计算机毕业设计ssm建设路小学芙童币和芙童印章管理系统(源码+系统+mysql数据库+Lw文档)
    无代码开发smardaten与Power Platform详细对比
    高中数学:三角恒等变换-两角和与差的变形公式
  • 原文地址:https://blog.csdn.net/aaaaaaaaanjjj/article/details/126707955