增加后置请求功能详情见使用说明

This commit is contained in:
jing song 2022-05-11 16:23:50 +08:00
parent 7367243e1b
commit bd6036b2f0
15 changed files with 273 additions and 63 deletions

View File

@ -87,7 +87,10 @@ QQ 529548204
下面示例为 关联接口为tradeAdd 与 tradeAdd2两条yaml数据中case的第一条和第二条
分别去这两个接口返回结果的 id 并命名为tradeId 、tradeId2
内存中会保存成字典格式
`{"tradeId":"10","tradeId2":"11"}`
`{"tradeId":"10","tradeId2":"11"}`
读取方法:`$relevance(tradeId2)$`
![img.png](pic/img.png)
```yaml
relevance:
@ -178,8 +181,7 @@ QQ 529548204
### 1.4 生成随机数据介绍
部分数据采用faker库生成
```python
int_num = "$RandomInt(1,333)$" # 267 int格式 不可和字符串拼接
int_num2 = "$RandomPosInt(1,333)$" # '267' 字符串格式
int_num = "$RandomPosInt(1,333)$" # 267
str_num = '$RandomString($RandomPosInt(2,23)$)$$RandomPosInt(1,333)$' # AbE3c14580f29aDFe5
float_num = '$RandomFloat($RandomPosInt(2,13)$,$RandomPosInt(2,13)$,$RandomPosInt(2,13)$)$' # 11.84864
time_num = '$GetTime(time_type=else,layout=%Y-%m-%d %H:%M:%S,unit=0,0,0,0,0)$' # 当前时间 2022-04-14 13:27:01
@ -193,7 +195,37 @@ QQ 529548204
phone_number = "$faker(phone_number)$" # 15070673645
name = "$faker(name)$" # 许云
```
### 1.5 整体yaml展示
### 1.4 后置请求处理
使用场景: 新增数据A之后一系列用例执行完需要后置来还原数据形成业务闭环.
datatype 有3种类型 param json 和urlparam
dataname为后置关联的参数中参数名称
path为当前请求返回结果的jsonpath 根据path的值更换后置处理参数值
如果不需要后置处理 teardown 置空即可
```yaml
teardown:
- tdName: pointDel # 其他testcase的ID
tdNum: 1 # 关联的case数组里 第几条数据
tddata:
- datatype: param
dataname: name
path: $.name
- datatype: urlparam
dataname: pointId
path: $.id
- tdName: pointAdd # 其他testcase的ID
tdNum: 1 # 关联的case数组里 第几条数据
tddata:
- datatype: json
dataname: name
path: $.name
- datatype: urlparam
dataname: pointId
path: $.id
```
# 三、文件展示
###3.1yaml文件展示
> !!!***所有case的id 务必唯一***!!!
@ -241,11 +273,28 @@ login: # caseID **请务必唯一**
relevance:
# 判断如果不需要关联relevance字段为空即可
# 如果需要关联就
- relCaseName: tradeAdd # 其他testcase的ID
relCaseNum: 1 # 关联的case数组里 第几条数据
value: $.id # 当前返回结果的jsonpath
name: tradeId # 关联值名称
response:
# 场景 接口A删除请求,需要接口B新增请求中返回的ID以及name
- relCaseName: pointAdd # 其他testcase的ID
relCaseNum: 1 # 关联的case数组里 第几条数据
reldata:
- value: $.id # 当前返回结果的jsonpath
name: pointId # 关联值名称
- value: $.name # 当前返回结果的jsonpath
name: pointname # 关联值名称
teardown:
- tdName: pointDel # 后置请求的caseID
tdNum: 1 # 后置请求的case数组里 第几条数据
tddata:
- datatype: param
dataname: name
path: $.name
- datatype: urlparam
dataname: pointId
path: $.id
- datatype: json
dataname: name
path: $.name
# 机制为 根据relevance字段生成字典{"tradeId":"123"}
# 使用关联值方法为将需要替换的地方修改为 $relevance(tradeId)$
@ -343,13 +392,13 @@ charset = utf8
webhook =
secret =
```
# 、操作方法
# 、操作方法
1. 新建config/config.ini文件 格式如上例子
2. 执行**util/scripts/newProject.py** 根据testname生成测试项目基础目录
3. 在生成的**test_suite/datas/*testname*** 文件夹下增加yaml测试用例
4. 执行**util/scripts/writeCase.py**生成测试脚本 关于token 需要根据自己项目情况修改yaml文件中token关键字 如果不需要token值为false 需要token则改为需要的类型
5. 执行**setupMain.py**开始测试
# 、代码展示
# 、代码展示
test_login.py
![自动生成吃的测试用例](https://img-blog.csdnimg.cn/39ebf79842e14cf791af0fa88433eab3.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6bq76L6j54Or5YS_,size_20,color_FFFFFF,t_70,g_se,x_16)
login.yml

BIN
pic/img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@ -21,4 +21,5 @@ shoucang:
info: 깊데헝헹
method: POST
cache:
relevance:
relevance:
teardown:

View File

@ -25,3 +25,4 @@ login:
path: # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值
name: 'cookie'
relevance:
teardown:

View File

@ -10,6 +10,8 @@ from util.tools.log import Log
from util.tools.readYamlFile import ini_allyaml
from common.basePage import apisend
from util.tools.iniRequests import relevance
from util.tools.iniHeaders import iniheaders
from util.tools.requestsTearDown import caseTearDown
alldata = ini_allyaml()
Log()
@ -24,4 +26,6 @@ __all__ = [
'apisend',
'alldata',
'relevance',
'iniheaders',
'caseTearDown',
]

View File

@ -35,6 +35,8 @@ from util.tools.log import Log
from util.tools.readYamlFile import ini_allyaml
from common.basePage import apisend
from util.tools.iniRequests import relevance
from util.tools.iniHeaders import iniheaders
from util.tools.requestsTearDown import caseTearDown
alldata = ini_allyaml()
Log()
@ -49,6 +51,8 @@ __all__ = [
'apisend',
'alldata',
'relevance',
'iniheaders',
'caseTearDown',
]""")
if "conftest.py" not in os.listdir(casepath):
with open(casepath + "/" + r"{}".format("conftest.py"), 'w', encoding='utf-8') as f:

View File

@ -66,6 +66,7 @@ class Counter:
case["host"] = "host" # 可参数化
case["address"] = url_param_list[0] # path
case["relevance"] = None
case["teardown"] = None
case["cache"] = None
case["headers"] = {}
case["headers"] = dict(headers)

View File

@ -50,12 +50,14 @@ class Test_{filename}(object):""")
# 判断是否需要token 默认类型是 Authorization
f.write(f"""
def test_{item}(self, casedata, setup_Login):
casedata["headers"] = iniheaders(casedata["headers"])
casedata["headers"]["{filedata[item]["token"]}"] = "$caches(cookie)$"
casedata = relevance(alldata, casedata, setup_Login)""")
elif filedata[item]["token"] and filedata[item]["token"] != "Cookies":
# 判断是否需要token 默认类型是 Authorization
f.write(f"""
def test_{item}(self, casedata, setup_Login):
casedata["headers"] = iniheaders(casedata["headers"])
casedata["headers"]["{filedata[item]["token"]}"] = setup_Login
casedata = relevance(alldata, casedata, setup_Login)""")
else:
@ -66,15 +68,29 @@ class Test_{filename}(object):""")
f.write("""
res,restime, code = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"],
headers=casedata["headers"],
data=casedata["data"], caches=casedata["cache"])
data=casedata["data"], caches=casedata["cache"])""")
if not filedata[item]["token"]:
f.write("""
caseTearDown(alldata,casedata,res)
asserting(hope_res=casedata["assert"], real_res=res, re_time=restime, re_code=code)\n""")
else:
f.write("""
caseTearDown(alldata,casedata,res,setup_Login)
asserting(hope_res=casedata["assert"], real_res=res, re_time=restime, re_code=code)\n""")
else:
f.write(f"""
res,restime, code = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"],
headers=casedata["headers"],
data=casedata["data"], caches=casedata["cache"],
files=casedata["data"]["file"])
asserting(hope_res=casedata["assert"], real_res=res, re_time=restime, re_code=code)\n""")
files=casedata["data"]["file"])""")
if not filedata[item]["token"]:
f.write("""
caseTearDown(alldata,casedata,res)
asserting(hope_res=casedata["assert"], real_res=res, re_time=restime, re_code=code)\n""")
else:
f.write("""
caseTearDown(alldata,casedata,res,setup_Login)
asserting(hope_res=casedata["assert"], real_res=res, re_time=restime, re_code=code)\n""")
if __name__ == '__main__':

View File

@ -10,6 +10,8 @@ import os
import shutil
import jsonpath
from util.tools.datasTypeChange import valueHandle, strHandleCookies
from util.tools.mkDir import mk_dir
from util.tools import root_path
@ -25,30 +27,6 @@ body: get请求 根据name replace参数 post 根据path 更新json数据
cachepath = root_path + dir_manage("${cache_dir}$")
def valueHandle(data: str):
"""
字符串转化成字典
url的param参数转化为字典
:param data:
:return:
"""
param_dict = {}
param_list = data.split("&")
for param in param_list:
param_dict[param.split("=")[0]] = param.split("=")[1]
return param_dict
def strHandle(data: dict):
"""
字典转化成字符串
:param data:
:return:
"""
cookies_str = ''
for k, v in data.items():
cookies_str = cookies_str + (k + "=" + v + ";")
return cookies_str
class Cache(object):
@ -156,7 +134,7 @@ class Cache(object):
# values = jsonpath.jsonpath(param, data['path'])
# if not values:
# raise ValueError("path错误")
cookie = strHandle(cookie)
cookie = strHandleCookies(cookie)
self.set(key=data["name"], value=cookie)
# else:
# values = jsonpath.jsonpath(valueHandle(param), data['path'])

View File

@ -0,0 +1,52 @@
# coding:utf-8
"""
@author: jing
@contact: 529548204@qq.com
@file: datasTypeChange.py
@time: 2022/5/11 15:05
"""
def valueHandle(data: str):
"""
url格式字符串转化成字典
url的param参数转化为字典
:param data:
:return:
"""
param_dict = {}
param_list = data.split("&")
for param in param_list:
param_dict[param.split("=")[0]] = param.split("=")[1]
return param_dict
def strHandleCookies(data: dict):
"""
字典转化成字符串
:param data:
:return:
"""
cookies_str = ''
for k, v in data.items():
cookies_str = cookies_str + (k + "=" + str(v) + ";")
return cookies_str
def strHandleUrl(data: dict):
"""
字典转化成字符串
:param data:
:return:
"""
url_str = ''
for k, v in data.items():
url_str = url_str + (k + "=" + str(v) + "&")
return url_str.strip("&")
if __name__ == '__main__':
d = {
"a":1,
"b":2
}
print(strHandleUrl(d))

17
util/tools/iniHeaders.py Normal file
View File

@ -0,0 +1,17 @@
# coding:utf-8
"""
@author: jing
@contact: 529548204@qq.com
@file: iniHeaders.py
@time: 2022/5/11 10:56
"""
def iniheaders(headers):
"""
处理请求头特殊情况 如果请求头为空 读取后并不是字典格式 所以需要处理
:param headers:
:return:
"""
if not headers:
headers={}
return headers

View File

@ -5,11 +5,17 @@
@file: iniRequests.py
@time: 2022/4/2 16:48
"""
import logging
from common.basePage import apisend
from util.tools.iniHeaders import iniheaders
from util.tools.randomData import replace_random
from util.tools.log import Log
import jsonpath
import allure
Log()
def relevance(alldata, relevancedata, headerdata=None):
"""
@ -19,31 +25,38 @@ def relevance(alldata, relevancedata, headerdata=None):
:param headerdata: 被关联接口可能需要的请求头
:return:
"""
reldatalist = relevancedata["relevance"]["response"]
reldict = {}
if reldatalist:
if relevancedata["relevance"]:
reldatalist = relevancedata["relevance"]["response"]
for reldata in reldatalist:
try:
relcase = alldata[reldata["relCaseName"]]["case"][reldata["relCaseNum"] - 1]
# 获取被关联请求数据
# if p["relevance"]:
# relevance(alldata,relevancedata=p,headerdata=headerdata)
with allure.step("执行关联接口"):
if relcase["relevance"]:
# 如果被关联请求数据仍然存在关联情况 递归
relcase = relevance(alldata, relevancedata=relcase, headerdata=headerdata)
if alldata[reldata["relCaseName"]]["token"]:
# 处理请求头
relcase["headers"] = iniheaders(relcase["headers"])
relcase["headers"][alldata[reldata["relCaseName"]]["token"]] = headerdata
# 进行关联接口请求
res, time, code = apisend(address=relcase["address"],
method=relcase["method"], headers=relcase["headers"], data=relcase["data"],
caches=relcase["cache"], host=relcase["host"])
for i in reldata["reldata"]:
reldict[i["name"]] = jsonpath.jsonpath(res, i["value"])[0]
relcase = alldata[reldata["relCaseName"]]["case"][reldata["relCaseNum"] - 1]
# 获取被关联请求数据
# if p["relevance"]:
# relevance(alldata,relevancedata=p,headerdata=headerdata)
with allure.step("执行关联接口"):
if relcase["relevance"]:
# 如果被关联请求数据仍然存在关联情况 递归
relcase = relevance(alldata, relevancedata=relcase, headerdata=headerdata)
if alldata[reldata["relCaseName"]]["token"]:
# 处理请求头
relcase["headers"][alldata[reldata["relCaseName"]]["token"]] = headerdata
# 进行关联接口请求
res, time, code= apisend(address=relcase["address"],
method=relcase["method"], headers=relcase["headers"], data=relcase["data"],
caches=relcase["cache"],host=relcase["host"])
reldict[reldata["name"]] = jsonpath.jsonpath(res, reldata["value"])[0]
logging.info(f"成功执行前置关联接口:{relcase['info']}")
# 处理结果存入字典中 得到{关联数据的name:关联数据的值}
except Exception as e:
logging.error(f"执行前置关联接口:{relcase['info']}错误,{e}")
raise
data = eval(replace_random(str(relevancedata),param=reldict))
data = eval(replace_random(str(relevancedata), param=reldict))
# 将当前请求根据正则匹配 例: relevancedata 中包含 $relevance(id)$ reldict={id:1} 处理后 $relevance(id)$ 被替换为1
# 返回关联之完成 处理后的数据
return data
@ -53,7 +66,8 @@ if __name__ == '__main__':
d = {'info': '固件详情', 'host': 'host', 'address': '/v1/device/firmware/$url(firmwareId)$/', 'method': 'get',
'cache': None,
'relevance': [{'relCaseName': 'firmwareList', 'relCaseNum': 1, 'value': '$.data[0].id', 'name': 'firmwareId'},
{'relCaseName': 'firmwareList', 'relCaseNum': 1, 'value': '$.data[0].id', 'name': 'firmwareId2'}],
{'relCaseName': 'firmwareList', 'relCaseNum': 1, 'value': '$.data[0].id',
'name': 'firmwareId2'}],
'headers': {'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': None},
'data': {'param': None, 'urlparam': {'firmwareId': '$relevance(firmwareId)$'}}, 'assert': {
'jsonpath': [{'path': '$.msg', 'value': 'Success.', 'asserttype': '==', 'relevanceCheck': None},

View File

@ -26,6 +26,8 @@ def ini_allyaml():
for k,v in file_data.items():
if k in alldata:
raise Exception("存在重复case")
if not file_data["headers"]:
file_data["headers"] = {}
alldata= {**alldata,**file_data}
except UnicodeDecodeError:
with open(file, 'r') as f:
@ -55,7 +57,7 @@ if __name__ == '__main__':
# print(datapath)
# get_yaml_data(r"F:\api2.0\config\runConfig.yml")
d = ini_allyaml()
print(type(d))
print(d)
# # case_level = runConfig_dict[0]["address"].format(**{"home_id": "123"})
# print(runConfig_dict)

View File

@ -0,0 +1,71 @@
# coding:utf-8
"""
@author: jing
@contact: 529548204@qq.com
@file: requestsTearDown.py
@time: 2022/5/11 13:28
"""
import logging
from common.basePage import apisend
from util.tools.iniHeaders import iniheaders
from util.tools.datasTypeChange import strHandleUrl, valueHandle
from util.tools.log import Log
import jsonpath
import allure
Log()
def caseTearDown(alldata, casedata, caseres, headerdata=None):
"""
使用场景: 新增数据A之后一系列用例执行完需要后置来还原数据形成业务闭环.
datatype 有3种类型 param json 和urlparam
dataname为后置关联的参数中参数名称
path为当前请求返回结果的jsonpath 根据path的值更换后置处理参数值
如果不需要后置处理 teardown 置空即可
:param alldata: 全部数据
:param casedata: 测试数据
:param caseres: 当前用例返回结果
:param headerdata: 请求头数据
:return:
"""
teardowndata = casedata["teardown"]
if teardowndata:
for req in teardowndata:
tdcase = alldata[req["tdName"]]["case"][req["tdNum"] - 1]
try:
with allure.step("执行后置接口"):
if alldata[req["tdName"]]["token"]:
# 处理请求头
tdcase["headers"] = iniheaders(tdcase["headers"])
tdcase["headers"][alldata[req["tdName"]]["token"]] = headerdata
for tddata in req["tddata"]:
# 循环处理后置请求参数
value = jsonpath.jsonpath(caseres, tddata["path"])[0]
if tddata["datatype"] == "param":
# 后置参数为param类型时
middata = valueHandle(tdcase["data"]["param"])
# 为了方便处理 转化为字典 然后更换值之后再转化为param字符串
middata[tddata['dataname']] = value
tdcase["data"]["param"] = strHandleUrl(middata)
elif tddata["datatype"] == "json":
# 后置参数为json类型时
if not isinstance(tdcase["data"]["param"],dict):
raise TypeError("['data']['param']格式错误")
tdcase["data"]["param"][tddata['dataname']] = value
elif tddata["datatype"] == "urlparam":
# 后置参数为url路径类型时
tdcase["data"]["urlparam"] = {
tddata['dataname']: value
}
# 执行后置请求
apisend(address=tdcase["address"],
method=tdcase["method"], headers=tdcase["headers"],
data=tdcase["data"],
caches=tdcase["cache"], host=tdcase["host"])
logging.info(f"成功执行后置接口:{tdcase['info']}")
except Exception as e:
logging.error(f"执行后置接口:{tdcase['info']}错误,{e}")
raise

View File

@ -5,7 +5,7 @@ from email.header import Header
from email.mime.text import MIMEText
from config.confManage import mail_manage
from util.tools.log import Log
from scripts.log import Log
Log()