基于数据驱动的接口自动化框架封装

每天进步一点点,关注我们哦,每天分享测试技术文章

码同学抖音 :小码哥聊软件测试

1.数据驱动框架设计

1.框架结构

  • common: 这是一个package,主要用来存储所有的底层代码封装
  • logs: 这是一个目录,主要用来存放日志文件
  • report: 这是一个目录,里边的data表示测试结果数据,里边的html表示测试 告,注意这两个目录都是每次执行测试时自动生成的
  • testcases: 这是一个目录,主要用来存储excel文件,excel文件里是接口测试的相关数据
  • conftest.py: 重写pytest自带的一个内置函数的,统一管理自定义fixture的
  • pytest.ini: pytest相关的配置参数
  • run.py: 是整个框架执行的入口
  • 2.excel数据规则设计

    按照一定的维度进行分类,每个分类可以当做一个sheet工作表

  • 全局变量
  • 主要用来管理我们的公共数据
  • 变量名称

    变量值

    host

    http://82.xxx74.xx:xxxx

    username

    18866668888

    password

    123456

  • 接口默认参数
  • 通常在一个项目中,参数如果很多的时候,我们针对测试用例去传递数据就会很麻烦,所以我们针对每个接口的默认参数数据进行单独管理,在测试时只需要针对当前测试用例传递你要测试的某个字段值即可,其他字段统统来自于默认参数
  • 填写参数的规则:对于接口参数可能会有多种类型,表单的,查询的,json的,文件的等等
  • 表单类型时:

    {    "data":{        "xxx":"xxjsdhdh"    }}

    查询参数:

    {    "params":{        "xxx":"xxjsdhdh"    }}

    json参数:

    { “json”:{ “xxx”:”xxjsdhdh” }}

    混合参数,比如既有表单又有查询:

    {    "params":{        "xxx":"xxjsdhdh"    },    "data":{     "ddd":"ddff"    }}

    接口名称

    默认参数

    登录

    { “data”:{ “username”:”${username}”, “password”:”${password}” } }

    新增客户

    { “json”:{ “entity”: { “customer_name”: “沙陌001”, “mobile”: “18729399607”, “telephone”: “01028375678”, “website”: “http://mtongxue.com/”, “next_time”: “2022-05-12 :00”, “remark”: “这是备注”, “address”: “北京市,北京城区,昌平区”, “detailAddress”: “霍营地铁口”, “location”: “”, “lng”: “”, “lat”: “” } } }

    新建联系人

    { “json”:{ “entity”: { “name”: “沙陌001联系人”, “customer_id”:”${customerId}”, “mobile”: “18729399607”, “telephone”: “01028378782”, “email”: “sdsdd@qq.com”, “post”: “采购部员工”, “address”: “这是地址”, “next_time”: “2022-05-10 :00”, “remark”: “这是备注” } } }

    新建产品

    { “json”:{ “entity”: { “name”: “python全栈自动化”, “category_id”: 23, “num”: “98888”, “price”: “6980”, “description”: “接口/web/app/持续集成” } } }

  • 测试集合管理
  • 测试集合管理 主要是为了控制要执行哪些测试集合,以及测试集合执行的顺序
  • 测试集合名称:对应的就是某个测试集合的sheet工作表名称
  • 是否执行:只有值是y时才会被执行,其他值不会被执行
  • 测试集合名称是否执行新增客户接口测试集合y新建联系人接口测试集合y新建产品接口测试集合y
  • 测试集合
  • 每个测试集合在excel里是一个单独的sheet工作表,他负责某个模块或者某个接口相关的测试用例数据管理,一个测试集合中是可以存在多个测试用例的
  • 序 :仅仅只是个标识,没啥作用
  • 用例名称:一个用例可能会有多个接口的先后调用,在excel里一行数据就是针对一个接口的调用,多行数据就是多个接口的调用,如果一个用例需要用多行数据,那么这几行的用例名称保持一致
  • 接口名称:该列主要是为了和接口默认参数中的接口名称进行关联,通过接口名称得到该接口对应的默认参数,然后再根据测试数据来决定参数是什么
  • 接口地址:表示接口地址,在接口地址里域名几乎都是相同的,或者说是公共的,所以我们将域名作为了公共变量进行存储,那么在这里需要调用公共变量域名,调用方式${host},host就是公共变量中的一个变量
  • 请求方式:get/post/put/delete
  • 接口头信息:指的就是headers,对于一个接口来说不一定有特殊的头信息,那么就不填,如果有需要按照如下格式进行填写,以json格式字符串的方式:
  • token是要从登录接口的返回值中提取的,提取之后保存到一个变量中,咱们这里保存的变量名称叫做token,所以在这里引用了变量token,引用方式就是${token}
  • {“Admin-Token”:”${token}”}
  • 假如特殊的头信息有多个,写法就是在json字符串中继续追加键值对,比如:
  • {“Admin-Token”:”${token}”,”Content-Type”:”application/json”}
  • 测试数据:指的是在接口发起调用时传递的你要测试的某个数据,对于一个接口来说参数有很多,但是我们每次测试时,可能只是针对一两个参数进行测试,可以借助之前所学的通过jsonpath去匹配某些参数,并且替换他们的值
  • 设计思路:
  • 一个json格式的字符串,其中参数类型分为data、json、params、files
  • 其中的key是你要替换的目标参数对应的jsonpath,value就是该参数对应的新值,也就是测试数据
  • { “json”:{ “$.entity.customer_id”:999999999, }}
  • 如果要替换多个参数:
  • { “json”:{ “$.entity.name”:”自动化${{cur_timestamp()}}”, “$.entity.num”:”${timestamp}}” }}
  • 响应提取:响应提取是为了从当前接口的返回值中提取某些信息,保存在变量中,以便后续接口要使用时进行变量引用,这也是我们常说的关联
  • 比如每个接口都要用到token,token是登录接口产生的,所以登录的响应提取里要写提取内容,规则是以json格式的字符串作为标准格式,其中key是要保存的变量名称,value是要提取的参数对应的jsonpath表达式
  • { “token”:”$.Admin-Token”}
  • 期望响应状态码:http响应状态码期望值
  • 期望响应信息:表示我们要针对接口的响应信息中的某些参数进行断言,设计规则如下:
  • 依然是json格式的字符串,最外层是一个列表,里边套的是多个字典,一个字典就是一个参数的断言。
  • 每个字典的格式是必须包括两个键值对,一个actual表示实际值的key,实际值的value是参数对应的jsonpath表达式,一个expect表示期望值的key,期望值的value是期望内容
  • 2.数据驱动框架底层代码实现

    1.创建项目

    依赖于设计去创建项目结构

    2.excel数据读取

    在common这个package下创建一个python文件,叫做testcase_util.py

    # !/usr/bin python3                                # encoding: utf-8 -*-  # @file     : testcase_util.py                      # @author   : 沙陌 Matongxue_2# @Time     : 2022-05-10 11:27# @Copyright: 北京码同学import openpyxl# 读取全局变量sheet工作表def get_variables(wb):    sheet_data = wb['全局变量']    variables = {} # 用来存储读到的变量,名称是key,值是value    lines_count = sheet_data.max_row # 获取总行数    for l in range(2,lines_count+1):        key = sheet_data.cell(l,1).value        value = sheet_data.cell(l,2).value        variables[key] = value    return variablesdef get_api_default_params(wb):    sheet_data = wb['接口默认参数']    api_default_params = {} # 用来存储读到的变量,名称是key,值是value    lines_count = sheet_data.max_row # 获取总行数    for l in range(2,lines_count+1):        key = sheet_data.cell(l,1).value        value = sheet_data.cell(l,2).value        api_default_params[key] = value    return api_default_params# 获取要执行的测试集合名称def get_casesuitename(wb):    sheet_data = wb['测试集合管理']    lines_count = sheet_data.max_row  # 获取总行数    cases_suite_name = [] # 用来存储要执行的测试集合名称    for l in range(2,lines_count+1):        flag = sheet_data.cell(l,2).value        if flag == 'y':            suite_name = sheet_data.cell(l,1).value            cases_suite_name.append(suite_name)    return cases_suite_name# 需要根据要执行的测试集合名称来读取对应的测试用例数据def read_testcases(wb,suite_name):    sheet_data = wb[suite_name]    lines_count = sheet_data.max_row  # 获取总行数    cols_count = sheet_data.max_column # 获取总列数    """    规定读出来的测试数据存储结构如下:    {        “新增客户正确”:[            ['apiname','接口地址','请求方式','头信息',....],            ['apiname','接口地址','请求方式','头信息',....],        ],        "新增客户失败-用户名为空":[            ['apiname','接口地址','请求方式','头信息',....]        ],        "新增客户失败-手机 格式不正确":[            ['apiname','接口地址','请求方式','头信息',....]        ]    }    """    cases_info = {} #用来存储当前测试集合中的所有用例信息的    for l in range(2,lines_count+1):        case_name = sheet_data.cell(l,2).value # 测试用例名称        lines = [] # 用来存储当前行测试数据的        for c in range(3,cols_count+1):            cell = sheet_data.cell(l,c).value # 当前单元格数据            if cell == None: # 处理空单元格                cell = ''            lines.append(cell)        # 判断当前用例名称是否已存在于cases_info中        # 如果不存在,那就是直接赋值        # 否则就是在原来的基础上追加        if case_name not in cases_info:            cases_info[case_name] = [lines]        else:            cases_info[case_name].append(lines)    return cases_info# 整合所有要执行的测试用例数据,将其转成pytest参数化需要的数据结构格式def get_all_testcases(wb):    """    整合后的数据结构是    [        ['新增客户接口测试集合','新增客户正确',[[],[]]],        ['新增客户接口测试集合','新增客户失败-用户名为空',[[],[]]],        ['新增客户接口测试集合','新增客户失败-手机 格式不正确',[[],[]]],        ['新建产品接口测试集合','新建产品正确',[[],[]]],        ['新建产品接口测试集合','新建产品失败-产品编码重复',[[],[]]],    ]    :param wb:    :return:    """    test_data = [] # 用来存储所有测试数据    # 获取所有要执行的测试集合名称    cases_suite_name = get_casesuitename(wb)    for suite_name in cases_suite_name:        # 遍历读取每个要执行的测试集合sheet工作表中的测试用例数据        cur_cases_info = read_testcases(wb,suite_name) # 是个字典        for key,value in cur_cases_info.items():            # key实际上就是测试用例名称,value实际上测试用例多行数据信息            case_info = [suite_name,key,value]            test_data.append(case_info)    return test_dataif __name__ == '__main__':    wb = openpyxl.load_workbook('../testcases/CRM系统接口测试用例.xlsx')    # print(get_variables(wb))    # print(get_api_default_params(wb))    # print(get_casesuitename(wb))    # print(read_testcases(wb,'新增客户接口测试集合'))    print(get_all_testcases(wb))

    3.接口调用底层方法封装

    在common目录下创建一个client.py,写上如下代码

    # !/usr/bin python3                                # encoding: utf-8 -*-  # @file     : client.py                      # @author   : 沙陌 Matongxue_2# @Time     : 2022-05-11 10:01# @Copyright: 北京码同学import jsonpathimport requestssession = requests.session()class RequestsClient:    def send(self,url,method,**kwargs):        try:            self.resp = session.request(url=url,method=method,**kwargs)        except BaseException as e:            raise BaseException(f'接口发起异常:{e}')        return self.resp    # 针对jsonpath的数据提取封装一个方法    # 第一个参数指的是你要匹配的数据的jsonpath表达式    # 第二个指的是你想返回匹配到的第几个,默认是0返回第一个    def extract_resp(self,json_path,index=0):        # 注意有的接口是没有返回信息的,返回信息是空的        text = self.resp.text # 获取返回信息的字符串形式        if text != '':            resp_json = self.resp.json() # 获取响应信息的json格式            # 如果能匹配到值,那么res就是个列表            # 如果匹配不到res就是个False            res = jsonpath.jsonpath(resp_json,json_path)            if res:                if index < 0:                    # 如果index小于0 ,我认为你要匹配到的所有结果                    return res                else:                    return res[index]            else:                print('没有匹配到任何东西')        else:            raise BaseException('接口返回信息为空,无法提取')if __name__ == '__main__':    client = RequestsClient()    client.send(url= 'http://82.156.74.26:9099/login',                method='post',                data={'username':'18866668888','password':'123456'})    print(client.extract_resp('Admin-Token'))

    4.辅助函数封装及引用定义

    在我们测试时,有的参数并不能够写死,所以这个时候我们希望某个参数在每次执行时都是动态变化的,那么就需要我们封装一些辅助随机函数来帮我们完成数据的动态变化

    在common目录下建一个util_func.py的文件,在其中写上我们需要用到的辅助函数

    随机数生成我们可以用一个第三方库faker

    # !/usr/bin python3                                # encoding: utf-8 -*-  # @file     : run.py                      # @author   : 沙陌 Matongxue_2# @Time     : 2022-05-10 11:24# @Copyright: 北京码同学import hashlibimport timefrom faker import Fakerfake = Faker(locale='zh_CN')def rdm_phone_number():    return fake.phone_number()def cur_timestamp():#到毫秒级的时间戳    return int(time.time() * 1000)def cur_date():# 2021-12-25    return fake.date_between_dates()def cur_date_time():# 2021-12-25 10:07:33    return fake.date_time_between_dates()def rdm_date(pattern='%Y-%m-%d'):    return fake.date(pattern=pattern)def rdm_date_time():    return fake.date_time()def rdm_future_date_time(end_date):    return fake.future_datetime(end_date=end_date)def md5(data):    data = str(data)    return hashlib.md5(data.encode('UTF-8')).hexdigest()if __name__ == '__main__':    print(rdm_phone_number())    print(rdm_date())    print(rdm_date_time())    print(cur_date())    print(cur_timestamp())    print(cur_date_time())    print(rdm_future_date_time('+60d'))    print(md5('123456'))

    在excel中需要用到动态函数时,调用规则是${{md5(123456)}} 再比如${{rdm_future_date_time(+60d)}}

    免费领取 码同学软件测试 课程笔记+超多学习资料+完整视频+最新面试题,可以转发文章 + 私信「码同学666」获取资料哦

    5.excel中动态数据的正则替换

  • 正则表达式基本规则
  • 基本规则: https://baike.baidu.com/item/正则表达式/1700215
  • 在线正则调试:https://tool.oschina.net/regex
  • 代码封装
  • 在testcase_util.py文件中增加如下代码:
  • # 该方法是针对excel中数据中存在动态变量时,进行变量识别以及替换的def regx_sub(string,vars_dict): res = re.findall(r’${([A-Za-z_]+?)}’,string) for var_name in res: # print(var_name) value = vars_dict[var_name] # print(value) # 得到变量对应的值value,然后用字符串替换的方法替换 string = string.replace(f’${{{var_name}}}’,str(value)) return string# 针对excel数据中存在动态函数调用时,使用正则匹配并执行函数完成数据替换def regx_func_exec(string): res = re.findall(r’${{(.+?)((.*?))}}’,string) for func_method in res: print(func_method) func_name = func_method[0] # 函数名称 func_params = func_method[1] # 函数参数 # 使用python中的反射机制来实现函数执行 if hasattr(util_func,func_name): # 如果该函数在util_func这个文件中,那么我就去得到该函数对象 f_method = getattr(util_func,func_name) if func_params == ”: value = f_method() else: value =f_method(func_params) string = string.replace(f’${{{{{func_name}({func_params})}}}}’,str(value)) else: raise BaseException(f'{func_name}不存在’) return string# 统一的针对数据做动态变量和动态函数的替换def regx_sub_data(string,vars_dict): string = regx_sub(string,vars_dict) string = regx_func_exec(string) return string
  • 6.统一测试方法封装

    针对框架去封装一个执行测试的入口,这个入口是一个基于pytest参数化的测试用例,在run.py中实现

  • 补充json数据替换的方法
  • 在testcases_util.py中增加如下方法:

    def update_value_to_json(json_object,json_path,new_value):    json_path_expr = parse(json_path)    for match in json_path_expr.find(json_object):        path = match.path # 这是获取到匹配结果的路径        if isinstance(path,Index):            match.context.value[match.path.index] = new_value        elif isinstance(path,Fields):            match.context.value[match.path.fields[0]] = new_value    return json_object

  • 补充内置变量timestamp
  • 在testcases_util.py中修改下述方法

    def get_variables(wb):    sheet_data = wb['全局变量']    variables = {} # 用来存储读到的变量,名称是key,值是value    lines_count = sheet_data.max_row # 获取总行数    for l in range(2,lines_count+1):        key = sheet_data.cell(l,1).value        value = sheet_data.cell(l,2).value        variables[key] = value    # 增加一个内置变量,叫时间戳,注意这个时间戳是当前测试一运行就会产生,产生之后在当前测试未完成之前不管调用    # 多少次,都是一致的    variables['timestamp'] = cur_timestamp()    return variables

  • run.py里的代码
  • # !/usr/bin python3
    
                                                            

    声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

    上一篇 2022年5月17日
    下一篇 2022年5月17日

    相关推荐