forked from DxvLwRYF/apiAutoTest
更新path参数,请求参数数据依赖处理方式,减少冗余代码
This commit is contained in:
parent
c7930ea58e
commit
46ce8ddc6b
|
@ -57,13 +57,12 @@
|
|||
>>
|
||||
>> >`__init__.py`
|
||||
>> >
|
||||
>> >data_tearing.py:封装依赖数据与请求数据的逻辑处理,处理了path参数依赖,headers关键字参数的入参header
|
||||
>> >data_process.py:封装依赖数据与请求数据的逻辑处理,处理了path参数依赖,headers关键字参数的入参header
|
||||
>> >
|
||||
>> >read_config.py: 读取配置文件
|
||||
>> >
|
||||
>> >read_data.py: 读取excel用例文件
|
||||
>> >
|
||||
>> >save_response.py: 保存实际响应结果,处理依赖数据提取
|
||||
>> >
|
||||
>> >send_email.py : 发送邮件
|
||||
>> >
|
||||
|
@ -71,7 +70,6 @@
|
|||
>>
|
||||
>> venv: python 虚拟环境
|
||||
>>
|
||||
>> apiAutoTest用例说明.md: excel用例书写规则
|
||||
>>
|
||||
>> pytest.ini: pytest框架的一个可用配置,解决中文显示乱码
|
||||
>>
|
||||
|
@ -117,6 +115,7 @@ https://www.bilibili.com/video/BV1EE411B7SU?p=10
|
|||
#### 更新
|
||||
2020/08/08 增加实际响应存储数据的方法,并在字典可以处理依赖见tools/svae_response.py
|
||||
2020/08/09 实现多文件上传,接口中Path参数依赖处理
|
||||
2020/11/18 使用re库解决当请求参数层级结构多出现无法提取的bug,减少冗余代码,优化path路径参数提取,更新用例填写说明文档
|
||||
|
||||
#### 博客园首发
|
||||
https://www.cnblogs.com/zy7y/p/13426816.html
|
||||
|
|
|
@ -10,15 +10,17 @@
|
|||
from loguru import logger
|
||||
import requests
|
||||
|
||||
from tools.read_data import ReadData
|
||||
|
||||
|
||||
class BaseRequest(object):
|
||||
def __init__(self):
|
||||
# 修改时间:2020年9月14日17:09
|
||||
# 确保,整个接口测试中,使用同一个requests.Session() 来管理cookie
|
||||
# 修改时间:2020年9月14日17:09
|
||||
# 确保,整个接口测试中,使用同一个requests.Session() 来管理cookie
|
||||
self.session = requests.Session()
|
||||
|
||||
# 请求
|
||||
def base_requests(self, method, url, parametric_key=None, data=None, file_var=None, file_path=None, header=None):
|
||||
def send_requests(self, method, url, parametric_key=None, data=None, file_var=None, file_path=None, header=None):
|
||||
"""
|
||||
|
||||
:param method: 请求方法
|
||||
|
@ -35,6 +37,7 @@ class BaseRequest(object):
|
|||
"""
|
||||
# 修改时间:2020年9月14日17:09
|
||||
session = self.session
|
||||
|
||||
if (file_var in [None, '']) and (file_path in [None, '']):
|
||||
files = None
|
||||
else:
|
||||
|
@ -48,7 +51,6 @@ class BaseRequest(object):
|
|||
else:
|
||||
# 单文件上传
|
||||
files = {file_var: open(file_path, 'rb')}
|
||||
|
||||
if parametric_key == 'params':
|
||||
res = session.request(method=method, url=url, params=data, headers=header)
|
||||
elif parametric_key == 'data':
|
||||
|
@ -56,9 +58,7 @@ class BaseRequest(object):
|
|||
elif parametric_key == 'json':
|
||||
res = session.request(method=method, url=url, json=data, files=files, headers=header)
|
||||
else:
|
||||
raise ValueError('可选关键字为:get/delete/head/options/请求使用params, post/put/patch请求可使用json(application/json)/data')
|
||||
raise ValueError(
|
||||
'可选关键字为:get/delete/head/options/请求使用params, post/put/patch请求可使用json(application/json)/data')
|
||||
logger.info(f'请求方法:{method},请求路径:{url}, 请求参数:{data}, 请求文件:{files}, 请求头:{header})')
|
||||
return res.json()
|
||||
|
||||
|
||||
|
||||
return res.json()
|
|
@ -1,7 +1,8 @@
|
|||
server:
|
||||
test: http://127.0.0.1:8888/api/private/v1/
|
||||
# 实例代码使用的接口服务,已改为作者是自己的云服务器部署。(后端源码来自b站:https://www.bilibili.com/video/BV1EE411B7SU?p=10)
|
||||
dev: http://49.232.203.244:8888/api/private/v1/
|
||||
# https://space.bilibili.com/283273603 演示项目后端服务来自
|
||||
dev: http://www.ysqorz.top:8888/api/private/v1/
|
||||
|
||||
|
||||
# 实际响应jsonpath提取规则设置
|
||||
response_reg:
|
||||
|
|
Binary file not shown.
|
@ -6,6 +6,4 @@
|
|||
@file: __init__.py.py
|
||||
@ide: PyCharm
|
||||
@time: 2020/7/31
|
||||
"""
|
||||
|
||||
from loguru import logger
|
||||
"""
|
|
@ -13,27 +13,20 @@ from loguru import logger
|
|||
import pytest
|
||||
import allure
|
||||
from api.base_requests import BaseRequest
|
||||
from tools.data_tearing import TreatingData
|
||||
from tools.data_process import DataProcess
|
||||
from tools.read_config import ReadConfig
|
||||
from tools.read_data import ReadData
|
||||
from tools.save_response import SaveResponse
|
||||
|
||||
# 读取配置文件 对象
|
||||
rc = ReadConfig()
|
||||
base_url = rc.read_serve_config('dev')
|
||||
token_reg, res_reg = rc.read_response_reg()
|
||||
case_data_path = rc.read_file_path('case_data')
|
||||
report_data = rc.read_file_path('report_data')
|
||||
report_generate = rc.read_file_path('report_generate')
|
||||
log_path = rc.read_file_path('log_path')
|
||||
report_zip = rc.read_file_path('report_zip')
|
||||
email_setting = rc.read_email_setting()
|
||||
# 实例化存响应的对象
|
||||
save_response_dict = SaveResponse()
|
||||
# 读取excel数据对象
|
||||
data_list = ReadData(case_data_path).get_data()
|
||||
# 数据处理对象
|
||||
treat_data = TreatingData()
|
||||
data_list = ReadData.get_data()
|
||||
# 请求对象
|
||||
br = BaseRequest()
|
||||
logger.info(f'配置文件/excel数据/对象实例化,等前置条件处理完毕\n\n')
|
||||
|
@ -57,9 +50,9 @@ class TestApiAuto(object):
|
|||
logger.warning('报告已生成')
|
||||
|
||||
@pytest.mark.parametrize('case_number,case_title,path,is_token,method,parametric_key,file_var,'
|
||||
'file_path, parameters, dependent,data,expect', data_list)
|
||||
'file_path,data,expect', data_list)
|
||||
def test_main(self, case_number, case_title, path, is_token, method, parametric_key, file_var,
|
||||
file_path, parameters, dependent, data, expect):
|
||||
file_path, data, expect):
|
||||
|
||||
# 感谢:https://www.cnblogs.com/yoyoketang/p/13386145.html,提供动态添加标题的实例代码
|
||||
# 动态添加标题
|
||||
|
@ -67,24 +60,28 @@ class TestApiAuto(object):
|
|||
|
||||
logger.debug(f'⬇️⬇️⬇️...执行用例编号:{case_number}...⬇️⬇️⬇️️')
|
||||
with allure.step("处理相关数据依赖,header"):
|
||||
data, header, parameters_path_url = treat_data.treating_data(is_token, parameters, dependent, data, save_response_dict)
|
||||
|
||||
header = DataProcess.header
|
||||
allure.attach(json.dumps(header, ensure_ascii=False, indent=4), "请求头", allure.attachment_type.TEXT)
|
||||
path = DataProcess.handle_path(path)
|
||||
|
||||
data = DataProcess.handle_data(data)
|
||||
allure.attach(json.dumps(data, ensure_ascii=False, indent=4), "请求数据", allure.attachment_type.TEXT)
|
||||
|
||||
with allure.step("发送请求,取得响应结果的json串"):
|
||||
allure.attach(json.dumps(base_url + path + parameters_path_url, ensure_ascii=False, indent=4), "最终请求地址", allure.attachment_type.TEXT)
|
||||
res = br.base_requests(method=method, url=base_url + path + parameters_path_url, parametric_key=parametric_key, file_var=file_var, file_path=file_path,
|
||||
allure.attach(json.dumps(base_url + path, ensure_ascii=False, indent=4), "最终请求地址", allure.attachment_type.TEXT)
|
||||
res = br.send_requests(method=method, url=base_url + path, parametric_key=parametric_key, file_var=file_var, file_path=file_path,
|
||||
data=data, header=header)
|
||||
allure.attach(json.dumps(res, ensure_ascii=False, indent=4), "实际响应", allure.attachment_type.TEXT)
|
||||
|
||||
with allure.step("将响应结果的内容写入实际响应字典中"):
|
||||
save_response_dict.save_actual_response(case_key=case_number, case_response=res)
|
||||
allure.attach(json.dumps(save_response_dict.actual_response, ensure_ascii=False, indent=4), "实际响应字典", allure.attachment_type.TEXT)
|
||||
DataProcess.save_response(case_number, res)
|
||||
allure.attach(json.dumps(DataProcess.response_dict, ensure_ascii=False, indent=4), "实际响应字典", allure.attachment_type.TEXT)
|
||||
|
||||
# 写token的接口必须是要正确无误能返回token的
|
||||
if is_token == '写':
|
||||
with allure.step("从登录后的响应中提取token到header中"):
|
||||
treat_data.token_header['Authorization'] = jsonpath.jsonpath(res, token_reg)[0]
|
||||
DataProcess.handle_header(is_token, res, token_reg)
|
||||
|
||||
with allure.step("根据配置文件的提取响应规则提取实际数据"):
|
||||
really = jsonpath.jsonpath(res, res_reg)[0]
|
||||
|
|
|
@ -6,4 +6,21 @@
|
|||
@file: __init__.py.py
|
||||
@ide: PyCharm
|
||||
@time: 2020/7/31
|
||||
"""
|
||||
"""
|
||||
from jsonpath import jsonpath
|
||||
from loguru import logger
|
||||
|
||||
|
||||
def extractor(obj: dict, expr: str = '.') -> object:
|
||||
"""
|
||||
:param obj :json/dict类型数据
|
||||
:param expr: 表达式, . 提取字典所有内容, $.case 提取一级字典case, $.case.data 提取case字典下的data
|
||||
$.0.1 提取字典中的第一个列表中的第二个的值
|
||||
"""
|
||||
try:
|
||||
result = jsonpath(obj, expr)[0]
|
||||
except Exception as e:
|
||||
logger.error(f'提取不到内容,丢给你一个错误!{e}')
|
||||
result = None
|
||||
return result
|
||||
|
||||
|
|
|
@ -0,0 +1,277 @@
|
|||
#!/usr/bin/env/python3
|
||||
# -*- coding:utf-8 -*-
|
||||
"""
|
||||
@project: apiAutoTest
|
||||
@author: zy7y
|
||||
@file: data_process.py
|
||||
@ide: PyCharm
|
||||
@time: 2020/11/18
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
from tools import *
|
||||
from tools.read_config import ReadConfig
|
||||
|
||||
|
||||
class DataProcess:
|
||||
response_dict = {}
|
||||
header = {}
|
||||
null_header = {}
|
||||
|
||||
@classmethod
|
||||
def save_response(cls, key: str, value: object) -> None:
|
||||
"""
|
||||
保存实际响应
|
||||
:param key: 保存字典中的key,一般使用用例编号
|
||||
:param value: 保存字典中的value,使用json响应
|
||||
"""
|
||||
cls.response_dict[key] = value
|
||||
logger.info(f'添加key: {key}, 对应value: {value}')
|
||||
|
||||
@classmethod
|
||||
def handle_path(cls, path_str: str = '') -> str:
|
||||
"""路径参数处理
|
||||
:param path_str: 带提取表达式的字符串 /&$.case_005.data.id&/state/&$.case_005.data.create_time&
|
||||
上述内容表示,从响应字典中提取到case_005字典里data字典里id的值,假设是500,后面&$.case_005.data.create_time& 类似,最终提取结果
|
||||
return /511/state/1605711095
|
||||
"""
|
||||
# /&$.case.data.id&/state/&$.case_005.data.create_time&
|
||||
path_str.split('/')
|
||||
for i in re.findall('&(.*?)&', path_str):
|
||||
path_str = path_str.replace(f'&{i}&', str(extractor(cls.response_dict, i)))
|
||||
logger.info(f'提取出的路径地址: {path_str}')
|
||||
return path_str
|
||||
|
||||
@classmethod
|
||||
def handle_header(cls, is_token: str, response: dict, reg) -> dict:
|
||||
"""处理header"""
|
||||
if is_token == '写':
|
||||
cls.header['Authorization'] = extractor(response, reg)
|
||||
return cls.header
|
||||
elif is_token == '':
|
||||
return cls.null_header
|
||||
else:
|
||||
return cls.header
|
||||
|
||||
@classmethod
|
||||
def handle_data(cls, variable: str) -> dict:
|
||||
"""请求数据处理
|
||||
:param variable: 请求数据,传入的是可转换字典/json的字符串,其中可以包含变量表达式
|
||||
return 处理之后的json/dict类型的字典数据
|
||||
"""
|
||||
if variable == '':
|
||||
return
|
||||
for i in re.findall('&(.*?)&', variable):
|
||||
variable = variable.replace(f'&{i}&', str(extractor(cls.response_dict, i)))
|
||||
if 'null' in variable:
|
||||
variable = variable.replace('null', 'None')
|
||||
if 'true' in variable:
|
||||
variable = variable.replace('true', 'True')
|
||||
if 'false' in variable:
|
||||
variable = variable.replace('false', 'False')
|
||||
logger.info(f'最终的请求数据如下: {variable}')
|
||||
print(variable)
|
||||
return eval(variable)
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
data = """{
|
||||
"case_001": {
|
||||
"data": null,
|
||||
"meta": {
|
||||
"msg": "参数错误",
|
||||
"status": 400
|
||||
}
|
||||
},
|
||||
"case_002": {
|
||||
"data": {
|
||||
"id": 500,
|
||||
"rid": 0,
|
||||
"username": "admin",
|
||||
"mobile": "12345678",
|
||||
"email": "adsfad@qq.com",
|
||||
"token": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjUwMCwicmlkIjowLCJpYXQiOjE2MDU3MTEwOTUsImV4cCI6MTYwNTc5NzQ5NX0.v4YsivyRT9I0kFnIIph7btKdTg7SfeeO6xsbhrGJC5w"
|
||||
},
|
||||
"meta": {
|
||||
"msg": "登录成功",
|
||||
"status": 200
|
||||
}
|
||||
},
|
||||
"case_003": {
|
||||
"data": {
|
||||
"total": 5,
|
||||
"pagenum": 1,
|
||||
"users": [
|
||||
{
|
||||
"id": 500,
|
||||
"role_name": "超级管理员",
|
||||
"username": "admin",
|
||||
"create_time": 1486720211,
|
||||
"mobile": "12345678",
|
||||
"email": "adsfad@qq.com",
|
||||
"mg_state": true
|
||||
},
|
||||
{
|
||||
"id": 502,
|
||||
"role_name": "测试角色2",
|
||||
"username": "linken",
|
||||
"create_time": 1486720211,
|
||||
"mobile": "1213213123",
|
||||
"email": "asdf@qq.com",
|
||||
"mg_state": false
|
||||
},
|
||||
{
|
||||
"id": 508,
|
||||
"role_name": "主管",
|
||||
"username": "asdf1",
|
||||
"create_time": 1511853015,
|
||||
"mobile": "123123",
|
||||
"email": "adfsa@qq.com",
|
||||
"mg_state": true
|
||||
},
|
||||
{
|
||||
"id": 509,
|
||||
"role_name": "test",
|
||||
"username": "asdf123",
|
||||
"create_time": 1511853353,
|
||||
"mobile": "1111",
|
||||
"email": "asdf@qq.com",
|
||||
"mg_state": false
|
||||
},
|
||||
{
|
||||
"id": 510,
|
||||
"role_name": "超级管理员",
|
||||
"username": "小明111",
|
||||
"create_time": 1605710570,
|
||||
"mobile": "13288888888",
|
||||
"email": "1232@qq.com",
|
||||
"mg_state": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"msg": "获取管理员列表成功",
|
||||
"status": 200
|
||||
}
|
||||
},
|
||||
"case_004": {
|
||||
"data": null,
|
||||
"meta": {
|
||||
"msg": "无效token",
|
||||
"status": 400
|
||||
}
|
||||
},
|
||||
"case_005": {
|
||||
"data": {
|
||||
"id": 511,
|
||||
"username": "tester_zy7y1213",
|
||||
"role_id": -1,
|
||||
"create_time": 1605711095
|
||||
},
|
||||
"meta": {
|
||||
"msg": "创建成功",
|
||||
"status": 201
|
||||
}
|
||||
},
|
||||
"case_006": {
|
||||
"data": {
|
||||
"id": 511,
|
||||
"rid": -1,
|
||||
"username": "tester_zy7y1213",
|
||||
"mobile": null,
|
||||
"email": null,
|
||||
"mg_state": 0
|
||||
},
|
||||
"meta": {
|
||||
"msg": "设置状态成功",
|
||||
"status": 200
|
||||
}
|
||||
},
|
||||
"case_007": {
|
||||
"data": {
|
||||
"id": 511,
|
||||
"rid": -1,
|
||||
"username": "tester_zy7y1213",
|
||||
"mobile": null,
|
||||
"email": null,
|
||||
"mg_state": 0
|
||||
},
|
||||
"meta": {
|
||||
"msg": "设置状态成功",
|
||||
"status": 200
|
||||
}
|
||||
},
|
||||
"case_008": {
|
||||
"data": null,
|
||||
"meta": {
|
||||
"msg": "删除成功",
|
||||
"status": 200
|
||||
}
|
||||
},
|
||||
"case_009": {
|
||||
"data": {
|
||||
"total": 5,
|
||||
"pagenum": 1,
|
||||
"users": [
|
||||
{
|
||||
"id": 500,
|
||||
"role_name": "超级管理员",
|
||||
"username": "admin",
|
||||
"create_time": 1486720211,
|
||||
"mobile": "12345678",
|
||||
"email": "adsfad@qq.com",
|
||||
"mg_state": true
|
||||
},
|
||||
{
|
||||
"id": 502,
|
||||
"role_name": "测试角色2",
|
||||
"username": "linken",
|
||||
"create_time": 1486720211,
|
||||
"mobile": "1213213123",
|
||||
"email": "asdf@qq.com",
|
||||
"mg_state": false
|
||||
},
|
||||
{
|
||||
"id": 508,
|
||||
"role_name": "主管",
|
||||
"username": "asdf1",
|
||||
"create_time": 1511853015,
|
||||
"mobile": "123123",
|
||||
"email": "adfsa@qq.com",
|
||||
"mg_state": true
|
||||
},
|
||||
{
|
||||
"id": 509,
|
||||
"role_name": "test",
|
||||
"username": "asdf123",
|
||||
"create_time": 1511853353,
|
||||
"mobile": "1111",
|
||||
"email": "asdf@qq.com",
|
||||
"mg_state": false
|
||||
},
|
||||
{
|
||||
"id": 510,
|
||||
"role_name": "超级管理员",
|
||||
"username": "小明111",
|
||||
"create_time": 1605710570,
|
||||
"mobile": "13288888888",
|
||||
"email": "1232@qq.com",
|
||||
"mg_state": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"meta": {
|
||||
"msg": "获取管理员列表成功",
|
||||
"status": 200
|
||||
}
|
||||
}
|
||||
}"""
|
||||
DataProcess.response_dict = json.loads(data)
|
||||
print(DataProcess.response_dict, type(DataProcess.response_dict))
|
||||
|
||||
ds = "$.case_001.meta, $.case_001.data, $.case_003.data.users.0.mobile"
|
||||
|
||||
varb = "{'name': 'zy7y', 'meta': &$.case_001.meta&, 'dd': '&$.case_003.data.users.0.mobile&'}"
|
||||
print(DataProcess.handle_data(varb, ds), type(DataProcess.handle_data(varb, ds)))
|
||||
print(DataProcess.handle_path('/&$.case_005.data.id&/state/&$.case_005.data.create_time&'))
|
|
@ -8,28 +8,30 @@
|
|||
@time: 2020/7/31
|
||||
"""
|
||||
import xlrd
|
||||
from test import logger
|
||||
from tools import *
|
||||
|
||||
|
||||
class ReadData(object):
|
||||
def __init__(self, excel_path):
|
||||
self.excel_file = excel_path
|
||||
self.book = xlrd.open_workbook(self.excel_file)
|
||||
excel_path = '../data/case_data.xlsx'
|
||||
|
||||
def get_data(self):
|
||||
@classmethod
|
||||
def get_data(cls):
|
||||
"""
|
||||
|
||||
:return: data_list - pytest参数化可用的数据
|
||||
"""
|
||||
data_list = []
|
||||
table = self.book.sheet_by_index(0)
|
||||
book = xlrd.open_workbook(cls.excel_path)
|
||||
table = book.sheet_by_index(0)
|
||||
for norw in range(1, table.nrows):
|
||||
# 每行第4列 是否运行
|
||||
if table.cell_value(norw, 3) != '否': # 每行第三列等于否将不读取内容
|
||||
value = table.row_values(norw)
|
||||
value.pop(3)
|
||||
# 配合将每一行转换成元组存储,迎合 pytest的参数化操作,如不需要可以注释掉 value = tuple(value)
|
||||
value = tuple(value)
|
||||
logger.info(f'{value}')
|
||||
data_list.append(value)
|
||||
data_list.append(list(value))
|
||||
return data_list
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
ReadData.get_data()
|
Loading…
Reference in New Issue