更新使用方法,增加多层关联逻辑处理

This commit is contained in:
jing song 2022-04-06 14:06:29 +08:00
parent 0be79f6f1e
commit 84db500e7d
8 changed files with 174 additions and 78 deletions

View File

@ -23,33 +23,36 @@ http=127.0.0.1:4444;https=127.0.0.1:4444;ftp=127.0.0.1:4444
## 目录结构
|--接口自动化测试框架 # 主目录
├─common
├─config
├─ common
├─ config
└─ config.ini
├─scripts
├─testsuite
├─datas
├─ testsuite
├─ datas
└─ 项目文件夹 名称同config中 testname一致
├─ urlData.yml # 所有 url 具体格式参考下面YAML URL格式说明
└─ login.yml # 用例数据 格式参考下面YAML PARAM格式说明
├─testcase
├─ testcase
└─ 项目文件夹 名称同config中 testname一致 # 测试用例
└─ test_login.py
└─recording 录制脚本文件夹放录制的接口文档
├─caches # 关联用的临时缓存文件夹
├─util # 常用工具 用例生成 接口录制
├─log
├─report
├─pytest.ini
├─requirements.txt
├─README.md
└─setupMain.py # 整体执行程序
├─ caches # 关联用的临时缓存文件夹
├─ util # 常用工具 用例生成 接口录制
├─ tools
└─ scripts
├─ log
├─ report
├─ pytest.ini
├─ requirements.txt
├─ README.md
└─ setupMain.py # 整体执行程序
## yaml param格式
所有case的id 务必唯一
``` yaml
login:
login: # caseID **请务必唯一**
name: "登录"
token: false # 判断此接口是否使用token false 或者"cookie"或者"Authorization"等
# token: "Authorization"
@ -60,13 +63,23 @@ login:
host: 'host'
address: '/v1/apps/$url(region_id)$/' # $url(region_id)$ 正则匹配参数中的路径参数
method: 'post'
relevance:
cache: # 本地缓存 使用之后不支持分布式
- cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取
path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值
name: 'code'
- cachefrom: 'response' # response : 从结果中获取 body : 从参数中获取
path: '$.data'
name: 'data'
relevance:
- relCaseName: tradeAdd # 其他testcase的ID
relCaseNum: 1 # 关联的case数组里 第几条数据
value: $.id # 当前返回结果的jsonpath
name: tradeId # 关联值名称
# 使用关联值方法为正则匹配$relevance(tradeId)$
headers: {
"Content-Type": "application/json"
}

View File

@ -23,7 +23,7 @@ class apiSend(object):
@staticmethod
def iniDatas(data):
if isinstance(data, dict):
data = json.dumps(data,ensure_ascii=False)
data = json.dumps(data, ensure_ascii=False)
if data is None:
return data
dataran = replace_random(data)
@ -33,7 +33,7 @@ class apiSend(object):
"""
post请求
:param host:
:param rel: 关联情况
:param caches: 关联情况
- cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取
path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值
name: 'code'
@ -52,6 +52,7 @@ class apiSend(object):
url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress
data_random = self.iniDatas(data["param"])
logging.info("请求地址:%s" % "" + url)
logging.info("请求方法:POST")
logging.info("请求头: %s" % str(header))
logging.info("请求参数: %s" % str(data_random))
with allure.step("POST请求"):
@ -59,7 +60,7 @@ class apiSend(object):
allure.attach(name="请求头", body=str(header))
allure.attach(name="请求参数", body=str(data_random))
if "multipart/form-data" in str(header.values()):
if isinstance(files,dict):
if isinstance(files, dict):
for i in files:
value = files[i]
if isinstance(value, int):
@ -75,7 +76,7 @@ class apiSend(object):
boundary='-----------------------------' + str(random.randint(int(1e28), int(1e29 - 1)))
)
header['Content-Type'] = multipart.content_type
response = requests.post(url=url, headers=header, timeout=timeout,data=multipart)
response = requests.post(url=url, headers=header, timeout=timeout, data=multipart)
elif files is None:
multipart = MultipartEncoder(data)
response = requests.post(url=url, data=multipart, headers={'Content-Type': multipart.content_type},
@ -89,24 +90,24 @@ class apiSend(object):
else:
response = requests.post(url=url, data=data_random, headers=header, timeout=timeout)
try:
if response.status_code != 200:
logging.info("请求接口结果: %s" % str(response.text))
return response.text
if response.status_code == 200 or response.status_code == 201:
res = response.json()
else:
logging.info("请求接口结果: %s" % str(response.json()))
self.rel.locallcache(caches, bodys=data_random, res=response.json())
return response.json(), response.elapsed.total_seconds()
res = response.text
logging.info("请求接口结果: %s" % str(res))
self.rel.locallcache(caches, bodys=data_random, res=res)
allure.attach(name="请求结果", body=str(res))
return res, response.elapsed.total_seconds(),response.status_code
except Exception as e:
logging.error(e)
logging.error(response.text)
raise
def get(self, address, caches, header, data, timeout=8, host="host"):
"""
get请求
:param host:
:param rel: 关联情况
:param caches: 关联情况
- cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取
path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值
name: 'code'
@ -123,6 +124,7 @@ class apiSend(object):
data_random = self.iniDatas(data["param"])
url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress
logging.info("请求地址:%s" % "" + url)
logging.info("请求方法:GET")
logging.info("请求头: %s" % str(header))
logging.info("请求参数: %s" % str(data_random))
with allure.step("GET请求接口"):
@ -133,9 +135,14 @@ class apiSend(object):
if response.status_code == 301:
response = requests.get(url=response.headers["location"])
try:
logging.info("请求接口结果: %s" % str(response.json()))
self.rel.locallcache(caches, bodys=data_random, res=response.json())
return response.json(), response.elapsed.total_seconds()
if response.status_code == 200 or response.status_code == 201:
res = response.json()
else:
res = response.text
logging.info("请求接口结果: %s" % str(res))
self.rel.locallcache(caches, bodys=data_random, res=res)
allure.attach(name="请求结果", body=str(res))
return res, response.elapsed.total_seconds(),response.status_code
except Exception as e:
logging.error(e)
logging.error(response.text)
@ -146,7 +153,7 @@ class apiSend(object):
put请求
:param host:
:param address:
:param rel: 关联情况
:param caches: 关联情况
- cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取
path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值
name: 'code'
@ -160,6 +167,7 @@ class apiSend(object):
iniaddress = replace_random(address, param=data["urlparam"])
url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress
logging.info("请求地址:%s" % "" + url)
logging.info("请求方法:PUT")
logging.info("请求头: %s" % str(header))
data_random = self.iniDatas(data["param"])
logging.info("请求参数: %s" % str(data_random))
@ -174,20 +182,24 @@ class apiSend(object):
else:
response = requests.put(url=url, data=data_random, headers=header, timeout=timeout, files=files)
try:
logging.info("请求接口结果: %s" % str(response.json()))
self.rel.locallcache(caches, bodys=data_random, res=response.json())
return response.json(), response.elapsed.total_seconds()
if response.status_code == 200 or response.status_code == 201:
res = response.json()
else:
res = response.text
logging.info("请求接口结果: %s" % str(res))
self.rel.locallcache(caches, bodys=data_random, res=res)
allure.attach(name="请求结果", body=str(res))
return res, response.elapsed.total_seconds(),response.status_code
except Exception as e:
logging.error(e)
logging.error(response.text)
raise
def delete(self, address, caches, header, data, timeout=8, host="host"):
"""
get请求
:param host:
:param rel: 关联情况
:param caches: 关联情况
- cachefrom: 'body' # response : 从结果中获取 body : 从参数中获取
path: '$.code' # body如果是 "id=2&path=haha" 会转换成字典 然后根据path使用jsonpath取值
name: 'code'
@ -201,6 +213,7 @@ class apiSend(object):
data_random = self.iniDatas(data["param"])
url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress
logging.info("请求地址:%s" % "" + url)
logging.info("请求方法:DELETE")
logging.info("请求头: %s" % str(header))
logging.info("请求参数: %s" % str(data_random))
with allure.step("DELETE请求接口"):
@ -210,16 +223,52 @@ class apiSend(object):
response = requests.delete(url=url, params=data_random, headers=header, timeout=timeout)
try:
logging.info("请求接口结果: %s" % str(response.json()))
self.rel.locallcache(caches, bodys=data_random, res=response.json())
return response.json(), response.elapsed.total_seconds()
if response.status_code == 200 or response.status_code == 201:
res = response.json()
else:
res = response.text
logging.info("请求接口结果: %s" % str(res))
self.rel.locallcache(caches, bodys=data_random, res=res)
allure.attach(name="请求结果", body=str(res))
return res, response.elapsed.total_seconds(),response.status_code
except Exception as e:
logging.error(e)
logging.error(response.text)
raise
def patch(self, address, caches, header, timeout=8, data=None, files=None, host="host"):
iniaddress = replace_random(address, param=data["urlparam"])
url = str(self.http_type) + "://" + host_manage(hos='${{{}}}$'.format(host)) + iniaddress
logging.info("请求地址:%s" % "" + url)
logging.info("请求方法:PATHC")
logging.info("请求头: %s" % str(header))
data_random = self.iniDatas(data["param"])
logging.info("请求参数: %s" % str(data_random))
with allure.step("PATCH请求接口"):
allure.attach(name="请求地址", body=url)
allure.attach(name="请求头", body=str(header))
allure.attach(name="请求参数", body=str(data_random))
if 'application/json' in header.values():
if data_random:
data_random = json.loads(data_random)
response = requests.patch(url=url, json=data_random, headers=header, timeout=timeout, files=files)
else:
response = requests.patch(url=url, data=data_random, headers=header, timeout=timeout, files=files)
try:
if response.status_code == 200 or response.status_code == 201:
res = response.json()
else:
res = response.text
logging.info("请求接口结果: %s" % str(res))
allure.attach(name="请求结果", body=str(res))
self.rel.locallcache(caches, bodys=data_random, res=res)
return res, response.elapsed.total_seconds(),response.status_code
except Exception as e:
logging.error(e)
logging.error(response.text)
raise
def __call__(self, address, method, headers, data,caches, **kwargs):
def __call__(self, address, method, headers, data, caches, **kwargs):
try:
if method == "post" or method == 'POST':
return self.post(address=address, data=data, header=headers, caches=caches, **kwargs)
@ -229,6 +278,8 @@ class apiSend(object):
return self.delete(address=address, data=data, header=headers, caches=caches, **kwargs)
elif method == "put" or method == 'PUT':
return self.put(address=address, data=data, header=headers, caches=caches, **kwargs)
elif method == "patch" or method == 'PATCH':
return self.patch(address=address, data=data, header=headers, caches=caches, **kwargs)
else:
raise TypeError(f"请求异常,检查yml文件method")
except Exception:
@ -238,15 +289,15 @@ class apiSend(object):
apisend = apiSend()
if __name__ == '__main__':
h = {
"Authorization":"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjE2OTRmYjlmLTUzNjYtNGZjZS1hODg4LTBlY2UxOThmZThhZSJ9.5_mD4abE-5iHsSr6RB9R8qaIRV7zidUFkpytyyd2cjSiQcrdJvAE_6GjU9Q_Xsr0JmTkSCTiefpFySguyk2E8Q",
"Authorization": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjE2OTRmYjlmLTUzNjYtNGZjZS1hODg4LTBlY2UxOThmZThhZSJ9.5_mD4abE-5iHsSr6RB9R8qaIRV7zidUFkpytyyd2cjSiQcrdJvAE_6GjU9Q_Xsr0JmTkSCTiefpFySguyk2E8Q",
"Content-Type": "multipart/form-data"
}
d = {
"param":"updateSupport=0",
"urlparam":None
}
ress = apisend(address="/api/system/user/importData",data=d,method="post", headers=h, host="parkyz.kkx88.cn",rel=None,files={"file":r"C:\Users\admin\Desktop\company_template_1648432270489.xlsx"})
"param": "updateSupport=0",
"urlparam": None
}
p ={'address': '/v1/enter/trade/', 'assert': {'jsonpath': None, 'sqlassert': None, 'time': 2, 'code': 201}, 'data': {'param': {'name': '行业名称$RandomString($RandomPosInt(2,6)$)$', 'desc': '备注$RandomString($RandomPosInt(2,8)$)$'}, 'urlparam': None}, 'headers': {'Content-Type': 'application/json', 'Authorization': 'GREEN eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyMiwiZXhwIjoxNjUxODMzNjY4LCJ1c2VybmFtZSI6ImRsMDAxIn0.Dbk1ddEXdmW1tRzxZLvFgJwsh0hek6HJjzCabStdnz0'}, 'host': 'host_HB', 'info': '新建行业', 'method': 'POST', 'cache': None, 'relevance': None}
ress = apisend(address=p['address'], data=p['data'], method=p['method'], headers=p['headers'],
caches=p['cache'],host=p['host'],)
print(ress)

View File

@ -133,11 +133,27 @@ def assert_sql(hope_res, real_res):
raise ValueError("请检查sqlassert格式,如不需要断言,此字段应为空.")
def asserting(hope_res, real_res, re_time=None, third_data=None, third_datas=None):
def assert_code(hope_res, real_res):
"""
:param hope_res:
:param real_res:
:return:
"""
hope_code = hope_res["code"]
try:
with allure.step("判断状态码"):
allure.attach(name="期望状态码", body=str(hope_code))
allure.attach(name='实际状态码', body=str(real_res))
assert real_res <= hope_code
logging.info("code断言通过, 期望状态码'{0}', 实际状态码'{1}'".format(hope_code, real_res))
except AssertionError:
logging.error("code断言未通过, 期望状态码'{0}', 实际状态码'{1}'".format(hope_code, real_res))
raise
def asserting(hope_res, real_res,re_code=None, re_time=None, third_data=None, third_datas=None):
"""
:param re_code:
:param hope_res: 期望结果
:param real_res: 实际结果
:param re_time: 实际响应时间
@ -151,6 +167,9 @@ def asserting(hope_res, real_res, re_time=None, third_data=None, third_datas=Non
assert_sql(hope_res, real_res)
if hope_res["time"]:
assert_time(hope_res, re_time)
if hope_res["code"]:
assert_code(hope_res, re_code)
if __name__ == '__main__':

View File

@ -31,10 +31,11 @@ import allure
import pytest
from common.checkResult import asserting
from scripts.log import Log
from scripts.readYamlFile import ini_yaml
from util.tools.log import Log
from util.tools.readYamlFile import ini_allyaml
from common.basePage import apisend
from util.tools.iniRequests import relevance
alldata = ini_allyaml()
Log()
__all__ = [
@ -42,9 +43,11 @@ __all__ = [
'asserting',
'Log',
'ini_yaml',
'logging',
'alldata',
'allure',
'apisend',
'alldata',
'relevance',
]""")
if casepath + "/" + r"{}".format("conftest.py") not in os.listdir(casepath):
with open(casepath + "/" + r"{}".format("conftest.py"), 'w', encoding='utf-8') as f:

View File

@ -57,17 +57,17 @@ class Test_{filename}(object):""")
def test_{item}(self, casedata):""")
if not filedata[item]["file"]:
f.write("""
res, restime = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"],
res,restime, code = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"],
headers=casedata["headers"],
data=casedata["data"], caches=casedata["cache"])
asserting(hope_res=casedata["assert"], real_res=res, re_time=restime)\n""")
asserting(hope_res=casedata["assert"], real_res=res, re_time=restime, re_code=code)\n""")
else:
f.write(f"""
res, restime = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"],
res,restime, code = apisend(host=casedata["host"], address=casedata["address"], method=casedata["method"],
headers=casedata["headers"],
data=casedata["data"], rel=casedata["relevance"],
files={{"file": casedata["data"]["file"]}})
asserting(hope_res=casedata["assert"], real_res=res, re_time=restime)\n""")
asserting(hope_res=casedata["assert"], real_res=res, re_time=restime, re_code=code)\n""")

View File

@ -8,7 +8,6 @@
from common.basePage import apisend
from util.tools.readYamlFile import ini_allyaml
from util.tools.randomData import replace_random
import simplejson
import jsonpath
@ -17,13 +16,18 @@ def relevance(alldata, relevancedata, headerdata=None):
reldict = {}
if reldatalist:
for i in reldatalist:
p = alldata[i["relCaseName"]]["case"][i["relCaseNum"] - 1]
# if p["relevance"]:
# relevance(alldata,relevancedata=p,headerdata=headerdata)
if p["relevance"]:
p = relevance(alldata, relevancedata=p, headerdata=headerdata)
if alldata[i["relCaseName"]]["token"]:
p["headers"][alldata[i["relCaseName"]]["token"]] = headerdata
res, time = apisend(host=p["host"], address=p["address"],
res, time, code= apisend(address=p["address"],
method=p["method"], headers=p["headers"], data=p["data"],
caches=p["cache"])
caches=p["cache"],host=p["host"])
v = jsonpath.jsonpath(res, i["value"])[0]
reldict[i["name"]] = v
d2 = replace_random(str(relevancedata),param=reldict)
@ -42,5 +46,9 @@ if __name__ == '__main__':
{'path': '$.code', 'value': 0, 'asserttype': '==', 'relevanceCheck': None}], 'sqlassert': None,
'time': 2}}
h = "JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxNiwidXNlcm5hbWUiOiJhZG1pbjIiLCJleHAiOjE2NTE0ODUyOTcsImVtYWlsIjoiYWRtaW5AZXhhbXBsZS5jb20ifQ.ZIUziDJlJXFHPrLB1DH37Y-axF-cRpTNPqfdEk_WQTA"
s = relevance(alldata=ini_allyaml(), relevancedata=d, headerdata=h)
print(type(s))
# s = relevance(alldata=ini_allyaml(), relevancedata=d, headerdata=h)
# print(type(s))
# r = {'address': '/v1/enter/trade/$url(tradeId)$/', 'data': {'file': None, 'param': {'name': '行业名称$RandomString($RandomPosInt(2,6)$)$', 'desc': '备注$RandomString($RandomPosInt(2,8)$)$'}, 'urlparam': {'tradeId': '$relevance(tradeId)$'}}, 'relevance': [{'relCaseName': 'tradeAdd', 'relCaseNum': 1, 'value': '$.id', 'name': 'tradeID'}]}
# rd = {'tradeID': 119}
# d2 = replace_random(str(r),param=rd)
# print(d2)

View File

@ -247,21 +247,21 @@ def replace_random(value, res=None, param=None):
pattern = re.compile(r'\$RandomInt\(' + i + r'\)\$')
key = str(random_int(i))
value = re.sub(pattern, key, value, count=1)
value = replace_random(value, res)
value = replace_random(value, res ,param)
elif len(posint_list):
# 获取整型数据替换
for i in posint_list:
pattern = re.compile(r'\$RandomPosInt\(' + i + r'\)\$')
key = str(random_int(i))
value = re.sub(pattern, key, value, count=1)
value = replace_random(value, res)
value = replace_random(value, res,param)
elif len(string_list):
# 获取字符串数据替换
for i in string_list:
pattern = re.compile(r'\$RandomString\(' + i + r'\)\$')
key = str(random_string(i))
value = re.sub(pattern, key, value, count=1)
value = replace_random(value, res)
value = replace_random(value, res,param)
elif len(float_list):
# 获取浮点数数据替换
@ -269,7 +269,7 @@ def replace_random(value, res=None, param=None):
pattern = re.compile(r'\$RandomFloat\(' + i + r'\)\$')
key = str(random_float(i))
value = re.sub(pattern, key, value, count=1)
value = replace_random(value, res)
value = replace_random(value, res,param)
elif len(time_list):
# 获取时间替换
@ -280,7 +280,7 @@ def replace_random(value, res=None, param=None):
value = re.sub(pattern, key, value, count=1)
else:
print("$GetTime$参数错误time_type, layout为必填")
value = replace_random(value, res)
value = replace_random(value, res,param)
elif len(choice_list):
# 调用choice方法
@ -288,14 +288,14 @@ def replace_random(value, res=None, param=None):
pattern = re.compile(r'\$Choice\(' + i + r'\)\$')
key = str(choice_data(i))
value = re.sub(pattern, key, value, count=1)
value = replace_random(value, res)
value = replace_random(value, res,param)
elif len(sqljson_list):
for i in sqljson_list:
pattern = re.compile(r'\$json\(' + i.replace('$', "\$").replace('[', '\[') + r'\)\$')
key = str(sql_json(i, res))
value = re.sub(pattern, key, value, count=1)
value = replace_random(value, res)
value = replace_random(value, res,param)
elif len(urlparam_list):
@ -305,7 +305,7 @@ def replace_random(value, res=None, param=None):
pattern = re.compile(r'\$url\(' + i + r'\)\$')
key = str(nomal(i, param))
value = re.sub(pattern, key, value, count=1)
value = replace_random(value, param=param)
value = replace_random(value,res, param=param)
elif len(relevance_list):
@ -315,7 +315,7 @@ def replace_random(value, res=None, param=None):
pattern = re.compile(r'\$relevance\(' + i + r'\)\$')
key = str(nomal(i, param))
value = re.sub(pattern, key, value, count=1)
value = replace_random(value, param=param)
value = replace_random(value,res, param=param)
elif len(caches_list):
# urls = "$url(home_id)$"
@ -324,14 +324,14 @@ def replace_random(value, res=None, param=None):
pattern = re.compile(r'\$caches\(' + i + r'\)\$')
key = str(caches(i))
value = re.sub(pattern, key, value, count=1)
value = replace_random(value,param=param)
value = replace_random(value,res,param=param)
elif len(idcard_list):
# 调用choice方法
for i in idcard_list:
pattern = re.compile(r'\$faker\(' + i + r'\)\$')
key = str(fakerdata(i))
value = re.sub(pattern, key, value, count=1)
value = replace_random(value, res)
value = replace_random(value, res,param)
else:
pass
return value

View File

@ -1,4 +1,5 @@
# coding:utf-8
import logging
import os
import yaml
@ -6,6 +7,7 @@ import yaml
from config.confManage import dir_manage
from util.scripts import root_path
from util.tools.getFileNames import getFilePathList
import logging
datapath = root_path + dir_manage("${test_suite}$") + dir_manage("${data_dir}$") + "/" + dir_manage("${test_name}$")
@ -22,7 +24,7 @@ def ini_allyaml():
with open(file, 'r') as f:
file_data = f.read()
alldata= {**alldata,**yaml.safe_load(file_data)}
print("*"*200)
logging.info("*"*200)
return alldata
def ini_yaml(filename, path=datapath):