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

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

本文章出自【码同学软件测试】

码同学公众号:自动化软件测试

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

1.数据驱动框架设计

1.框架结构

2.excel数据规则设计

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

变量名称

变量值

host

http://82.xxx74.xx:xxxx

username

18866668888

password

123456

表单类型时:

{    "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:00: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:00:00", "remark": "这是备注" } } }

新建产品

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



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中动态数据的正则替换


6.统一测试方法封装

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

在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


在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


# !/usr/bin python3                                # encoding: utf-8 -*-  # @file     : run.py                      # @author   : 沙陌 Matongxue_2# @Time     : 2022-05-10 11:24# @Copyright: 北京码同学import openpyxlimport pytestfrom common.client import RequestsClientfrom common.testcase_util import get_all_testcases, get_variables, get_api_default_params, regx_sub_data,     update_value_to_jsonwb = openpyxl.load_workbook('testcases/CRM系统接口测试用例.xlsx')# 获取所有的测试用例数据test_data = get_all_testcases(wb)variables = get_variables(wb) # 获取所有的公共变量,也用来存储测试过程中产生的动态变量api_default_params = get_api_default_params(wb) # 获取所有接口的默认参数数据@pytest.mark.parametrize('suite_name,case_name,case_info_list',test_data)def test_run(suite_name,case_name,case_info_list):    # 创建一个接口调用的对象    client = RequestsClient()    # case_info_list 是多个接口的数据,是一个列表    for case_info in case_info_list:        # case_info 其实也是一个列表,表示excel某一行的测试数据,从接口名称开始往后        # ['登录', '${host}/login', 'post', '', '', '{
"token":"$.Admin-Token"
}', 200, '[
{
"actual":"$.code",
"expect":0
}
]']        kwargs = {'verify':False} #verify表示忽略https的证书        api_name = case_info[0] # 接口名称        url = case_info[1] # 接口名称        url = regx_sub_data(url,variables) # 处理url中的动态变量及动态函数调用        method = case_info[2] # 接口请求方式        headers = case_info[3] # 接口头信息        if headers!='':            headers = regx_sub_data(headers,variables)            headers = eval(headers) # 将json格式的字符串转换成字典            kwargs['headers'] = headers        # 测试数据并不是接口发起时真正的全部参数,需要根据用户填入的要测试的数据和该接口对应的默认数据进行替换以及组合来达到        # 请求数据        api_default_param = api_default_params[api_name] # 获取当前行的接口对应的默认数据        if api_default_param != '':            api_default_param = regx_sub_data(api_default_param,variables)            api_default_param = eval(api_default_param)        test_params = case_info[4] # 测试数据        if test_params != '':            test_params = regx_sub_data(test_params,variables)            test_params = eval(test_params)            # 解析测试数据,通过jsonpath去替换默认参数中的数据            # 逻辑是遍历测试数据,判断测试数据中是哪种参数类型(data/params/json/files),根据参数类型去替换默认数据的对应的部分            if 'json' in test_params:                """                {                    "$.entity.name":"联系人${{cur_timestamp()}}",                }                """                for json_path,new_value in test_params['json'].items():                    api_default_param['json'] = update_value_to_json(api_default_param['json'],json_path,new_value)            if 'data' in test_params:                for json_path,new_value in test_params['data'].items():                    api_default_param['data'] = update_value_to_json(api_default_param['data'],json_path,new_value)            if 'params' in test_params:                for json_path,new_value in test_params['params'].items():                    api_default_param['params'] = update_value_to_json(api_default_param['params'],json_path,new_value)            if 'files' in test_params:                for json_path,new_value in test_params['files'].items():                    api_default_param['files'] = update_value_to_json(api_default_param['files'],json_path,new_value)        test_params = api_default_param        # 整合完成测试数据和默认数据之后,将他们分别存储kwargs中        if 'json' in test_params:            kwargs['json'] = test_params['json']        if 'data' in test_params:            kwargs['data'] = test_params['data']        if 'params' in test_params:            kwargs['params'] = test_params['params']        if 'files' in test_params:            kwargs['files'] = test_params['files']        resp = client.send(url=url,method=method,**kwargs) # 发起请求        expect_status = case_info[6]  # 期望的响应状态码        assert resp.status_code == expect_status        # print(resp.text)        extract_resp = case_info[5] # 响应提取        if extract_resp != '':            extract_resp = eval(extract_resp)            """            {            "token":"$.Admin-Token"            }            """            for key,value in extract_resp.items():                # key就是提取后要保存的变量名称                # value是你要提取的目标字段对应的jsonpath表达式                res = client.extract_resp(value)                variables[key] = res        expect_resp = case_info[7] # 期望的响应信息        if expect_resp != '':            expect_resp = regx_sub_data(expect_resp,variables)            expect_resp = eval(expect_resp)            """            [                {                "actual":"$.code",                "expect":500,                },                {                "actual":"$.msg",                "expect":"产品编号已存在,请校对后再添加!",                }            ]            """            for expect_info in expect_resp:                json_path = expect_info['actual']                actual_res = client.extract_resp(json_path)                expect_res = expect_info['expect']                pytest.assume(actual_res==expect_res,f'期望是{expect_res},实际是{actual_res}')if __name__ == '__main__':    pytest.main() # 该方法会自动扫描当前项目中的pytest.ini,根据其中的配置进行执行


7.集成日志收集

日志收集的目的是在我们用例有失败时,可以帮助我们去追溯问题产生的原因。日志都要收集哪些信息呢?

主要收集接口发起以及接口响应的各项信息,在什么地方去集成日志可以收集到这些信息?


8.allure测试报告集成

需要用到一个python的第三方库,叫做allure-pytest,所以先安装

在pytest.ini中追加allure结果数据收集的命令参数

addopts = -sv --alluredir ./report/data --clean-alluredir

--alluredir ./report/data :表示收集到的测试结果存放在report/data目录中

--clean-alluredir :表示每次执行收集结果前都先清除上一次的结果


需要用到allure的命令行工具,命令行工具下载地址:

https://github.com/allure-framework/allure2/releases

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

如果无法访问,那么就下载我提供的allure-2.11.0.zip

下载之后解压即可,解压以后去配环境变量path,配如下路径

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

配完以后,在命令行中输入allure --version能看到版本号,就说明配置好了

记得重启pycharm,在pycharm进入终端输入如下命令:

allure generate ./report/data -o ./report/html


报告打开:

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

每次命令行输入命令比较麻烦,可以直接将生成命令集成在代码中,修改run.py中的main里代码如下:

if __name__ == '__main__':    pytest.main() # 该方法会自动扫描当前项目中的pytest.ini,根据其中的配置进行执行    os.system('allure generate ./report/data -o ./report/html --clean')


增加测试用例的层级划分

修改run.py中的代码如下:

# !/usr/bin python3                                # encoding: utf-8 -*-  # @file     : run.py                      # @author   : 沙陌 Matongxue_2# @Time     : 2022-05-10 11:24# @Copyright: 北京码同学import osimport allureimport openpyxlimport pytestfrom common.client import RequestsClientfrom common.testcase_util import get_all_testcases, get_variables, get_api_default_params, regx_sub_data,     update_value_to_jsonwb = openpyxl.load_workbook('testcases/CRM系统接口测试用例.xlsx')# 获取所有的测试用例数据test_data = get_all_testcases(wb)variables = get_variables(wb) # 获取所有的公共变量,也用来存储测试过程中产生的动态变量api_default_params = get_api_default_params(wb) # 获取所有接口的默认参数数据@pytest.mark.parametrize('suite_name,case_name,case_info_list',test_data)def test_run(suite_name,case_name,case_info_list):    # 创建一个接口调用的对象    client = RequestsClient()    allure.dynamic.feature(suite_name) # 测试报告上会高于测试用例的层级展示    allure.dynamic.title(case_name) # 测试报告上表示测试用例的名称    # case_info_list 是多个接口的数据,是一个列表    for case_info in case_info_list:        # case_info 其实也是一个列表,表示excel某一行的测试数据,从接口名称开始往后        # ['登录', '${host}/login', 'post', '', '', '{
"token":"$.Admin-Token"
}', 200, '[
{
"actual":"$.code",
"expect":0
}
]']        kwargs = {'verify':False} #verify表示忽略https的证书        api_name = case_info[0] # 接口名称        url = case_info[1] # 接口名称        url = regx_sub_data(url,variables) # 处理url中的动态变量及动态函数调用        method = case_info[2] # 接口请求方式        headers = case_info[3] # 接口头信息        if headers!='':            headers = regx_sub_data(headers,variables)            headers = eval(headers) # 将json格式的字符串转换成字典            kwargs['headers'] = headers        # 测试数据并不是接口发起时真正的全部参数,需要根据用户填入的要测试的数据和该接口对应的默认数据进行替换以及组合来达到        # 请求数据        api_default_param = api_default_params[api_name] # 获取当前行的接口对应的默认数据        if api_default_param != '':            api_default_param = regx_sub_data(api_default_param,variables)            api_default_param = eval(api_default_param)        test_params = case_info[4] # 测试数据        if test_params != '':            test_params = regx_sub_data(test_params,variables)            test_params = eval(test_params)            # 解析测试数据,通过jsonpath去替换默认参数中的数据            # 逻辑是遍历测试数据,判断测试数据中是哪种参数类型(data/params/json/files),根据参数类型去替换默认数据的对应的部分            if 'json' in test_params:                """                {                    "$.entity.name":"联系人${{cur_timestamp()}}",                }                """                for json_path,new_value in test_params['json'].items():                    api_default_param['json'] = update_value_to_json(api_default_param['json'],json_path,new_value)            if 'data' in test_params:                for json_path,new_value in test_params['data'].items():                    api_default_param['data'] = update_value_to_json(api_default_param['data'],json_path,new_value)            if 'params' in test_params:                for json_path,new_value in test_params['params'].items():                    api_default_param['params'] = update_value_to_json(api_default_param['params'],json_path,new_value)            if 'files' in test_params:                for json_path,new_value in test_params['files'].items():                    api_default_param['files'] = update_value_to_json(api_default_param['files'],json_path,new_value)        test_params = api_default_param        # 整合完成测试数据和默认数据之后,将他们分别存储kwargs中        if 'json' in test_params:            kwargs['json'] = test_params['json']        if 'data' in test_params:            kwargs['data'] = test_params['data']        if 'params' in test_params:            kwargs['params'] = test_params['params']        if 'files' in test_params:            kwargs['files'] = test_params['files']        resp = client.send(url=url,method=method,**kwargs) # 发起请求        expect_status = case_info[6]  # 期望的响应状态码        assert resp.status_code == expect_status        # print(resp.text)        extract_resp = case_info[5] # 响应提取        if extract_resp != '':            extract_resp = eval(extract_resp)            """            {            "token":"$.Admin-Token"            }            """            for key,value in extract_resp.items():                # key就是提取后要保存的变量名称                # value是你要提取的目标字段对应的jsonpath表达式                res = client.extract_resp(value)                variables[key] = res        expect_resp = case_info[7] # 期望的响应信息        if expect_resp != '':            expect_resp = regx_sub_data(expect_resp,variables)            expect_resp = eval(expect_resp)            """            [                {                "actual":"$.code",                "expect":500,                },                {                "actual":"$.msg",                "expect":"产品编号已存在,请校对后再添加!",                }            ]            """            for expect_info in expect_resp:                json_path = expect_info['actual']                actual_res = client.extract_resp(json_path)                expect_res = expect_info['expect']                pytest.assume(actual_res==expect_res,f'期望是{expect_res},实际是{actual_res}')if __name__ == '__main__':    pytest.main() # 该方法会自动扫描当前项目中的pytest.ini,根据其中的配置进行执行    os.system('allure generate ./report/data -o ./report/html --clean')

免费领取码同学软件测试课程笔记+超多学习资料+学习完整视频,可以关注我们公众号哦:自动化软件测试

本文著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

展开阅读全文

页面更新:2024-05-04

标签:例数   接口   数据   变量   框架   名称   参数   客户   格式   测试   信息

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top