增加xml参数类型请求,增加加密处理装饰器

This commit is contained in:
jing song 2022-05-17 17:13:06 +08:00
parent 6140e7db7c
commit cacdc378c1
13 changed files with 154 additions and 28 deletions

View File

@ -18,7 +18,8 @@ QQ 529548204
allure allure
框架采用python的pytest模块 框架采用python的pytest模块
搭配requests以及allure测试报告可以发送钉钉通知邮件通知 搭配requests以及allure测试报告可以发送钉钉通知邮件通知
支持自定义接口加密util.tools.encryption文件内自己编辑加密规则
可以根据yaml测试数据自动生成用例 可以根据yaml测试数据自动生成用例
支持接口关联, 支持接口关联,
支持类似jmeter的函数助手 支持类似jmeter的函数助手
@ -115,15 +116,19 @@ QQ 529548204
### 1.3 参数介绍 ### 1.3 参数介绍
`file` : 通过case外关键字file判断是否需要上传文件 如果需要则格式为:`{上传文件的参数名:文件路径}` `file` : 通过case外关键字file判断是否需要上传文件 如果需要则格式为:`{上传文件的参数名:文件路径}`
`param`:包含两种请求格式 `param`:包含两种请求格式
1json格式 `{ 1json格式 :
`{
"username": "finsiot","password": "$caches(pwd)$" # 读取缓存值 "username": "finsiot","password": "$caches(pwd)$" # 读取缓存值
}` }`(同样适用于xml格式 会根据请求头***application/xml或者text/xml*** 将字典转换成xml类型)
2param格式 `username=admin&password=123` 2param格式
`username=admin&password=123`
`urlparam``{ `urlparam`为路径参数`{
id: 123 id: 123
}` }`
会根据字典转换成
路径参数 **address**中 `v1/api/$url(id)$/`中会根据id替换为123 路径参数 **address**中 `v1/api/$url(id)$/`中会根据id替换为123
@ -186,7 +191,7 @@ QQ 529548204
time: 2 # 响应时间断言 time: 2 # 响应时间断言
code: 200 code: 200
``` ```
### 1.4 生成随机数据介绍 ### 1.5 生成随机数据介绍
部分数据采用faker库生成 部分数据采用faker库生成
```python ```python
int_num = "$RandomPosInt(1,333)$" # 267 int_num = "$RandomPosInt(1,333)$" # 267
@ -204,7 +209,7 @@ QQ 529548204
name = "$faker(name)$" # 许云 name = "$faker(name)$" # 许云
``` ```
### 1.4 后置请求处理 ### 1.6 后置请求处理
使用场景: 新增数据A之后一系列用例执行完需要后置来还原数据形成业务闭环. 使用场景: 新增数据A之后一系列用例执行完需要后置来还原数据形成业务闭环.
datatype 有3种类型 param json 和urlparam datatype 有3种类型 param json 和urlparam
dataname为后置关联的参数中参数名称 dataname为后置关联的参数中参数名称
@ -415,14 +420,20 @@ http=127.0.0.1:4444;https=127.0.0.1:4444;ftp=127.0.0.1:4444
HTTPS: HTTPS:
http://mitm.it/ 开启代理后下载证书安装 http://mitm.it/ 开启代理后下载证书安装
执行脚本 recording.py 每个请求将会在 ./test_suite/recording 文件夹中建立文件 每次执行录制时会覆盖原文件 执行脚本 recording.py 每个请求将会在 ./test_suite/recording 文件夹中建立文件 每次执行录制时会覆盖原文件
# 五、接口加密(测试版)
# 五、操作方法 加签加密法:
在config中`encryption`增加sign的值 为签
需要在util.tools.encryption文件内自己编辑加密规则
![img_2.png](pic/img_2.png)
需要加密的接口 YAML数据中encryption的值为true 会根据此在请求用例前增加加密装饰器
![img_1.png](pic/img_1.png)
# 六、操作方法
1. 新建config/config.ini文件 格式如上例子 1. 新建config/config.ini文件 格式如上例子
2. 执行**util/scripts/newProject.py** 根据testname生成测试项目基础目录 2. 执行**util/scripts/newProject.py** 根据testname生成测试项目基础目录
3. 在生成的**test_suite/datas/*testname*** 文件夹下增加yaml测试用例 3. 在生成的**test_suite/datas/*testname*** 文件夹下增加yaml测试用例
4. 执行**util/scripts/writeCase.py**生成测试脚本 关于token 需要根据自己项目情况修改yaml文件中token关键字 如果不需要token值为false 需要token则改为需要的类型 4. 执行**util/scripts/writeCase.py**生成测试脚本 关于token 需要根据自己项目情况修改yaml文件中token关键字 如果不需要token值为false 需要token则改为需要的类型
5. 执行**setupMain.py**开始测试(单用例调试时需要执行**util\tools\readYamlFile.py**将测试数据存入redis) 5. 执行**setupMain.py**开始测试(单用例调试时需要执行**util\tools\readYamlFile.py**将测试数据存入redis)
# 六、代码展示 # 、代码展示
test_login.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) ![自动生成吃的测试用例](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 login.yml

View File

@ -3,6 +3,7 @@ import json
import logging import logging
import os import os
import random import random
import xmltodict
import allure import allure
import requests import requests
@ -101,6 +102,12 @@ class apiSend(object):
if data_random: if data_random:
data_random = json.loads(data_random) data_random = json.loads(data_random)
response = requests.post(url=url, json=data_random, headers=header, timeout=timeout) response = requests.post(url=url, json=data_random, headers=header, timeout=timeout)
elif 'application/xml' in str(header.values()) or 'text/xml' in str(header.values()):
if data_random:
data_random = json.loads(data_random)
xmldata = xmltodict.unparse({'xml': data_random})
# dict转成xml
response = requests.post(url=url, data=xmldata, headers=header, timeout=timeout)
else: else:
response = requests.post(url=url, data=data_random, headers=header, timeout=timeout) response = requests.post(url=url, data=data_random, headers=header, timeout=timeout)
try: try:
@ -193,6 +200,12 @@ class apiSend(object):
if data_random: if data_random:
data_random = json.loads(data_random) data_random = json.loads(data_random)
response = requests.put(url=url, json=data_random, headers=header, timeout=timeout, files=files) response = requests.put(url=url, json=data_random, headers=header, timeout=timeout, files=files)
elif 'application/xml' in str(header.values()) or 'text/xml' in str(header.values()):
if data_random:
data_random = json.loads(data_random)
xmldata = xmltodict.unparse({'xml': data_random})
# dict转成xml
response = requests.post(url=url, data=xmldata, headers=header, timeout=timeout)
else: else:
response = requests.put(url=url, data=data_random, headers=header, timeout=timeout, files=files) response = requests.put(url=url, data=data_random, headers=header, timeout=timeout, files=files)
try: try:

View File

@ -83,7 +83,17 @@ def dingding_manage(dingding):
pass pass
return dingding return dingding
def encryption_manage(encryption):
try:
relevance_list = re.findall(r"\${(.*?)}\$", encryption)
for n in relevance_list:
pattern = re.compile(r'\${' + n + r'}\$')
dir_cf = Config()
dir_relevance = dir_cf.read_encryption()
encryption = re.sub(pattern, dir_relevance[n], encryption, count=1)
except TypeError:
pass
return encryption
if __name__ == '__main__': if __name__ == '__main__':
# print(dir_manage("${pro_dir}$")) # print(dir_manage("${pro_dir}$"))
# print(db_manage("${user}$")) # print(db_manage("${user}$"))
@ -91,6 +101,6 @@ if __name__ == '__main__':
# print(db_manage("${database}$")) # print(db_manage("${database}$"))
# print(db_manage("${charset}$")) # print(db_manage("${charset}$"))
# print(int(db_manage("${port}$"))) # print(int(db_manage("${port}$")))
print(host_manage("${host}$${host_117}$")) print(encryption_manage("${sign}$"))
# print("${{{haha}}}$".format(**{"haha":"123"})) # print("${{{haha}}}$".format(**{"haha":"123"}))
# print(host_manage("${{{haha}}}$".format(**{"haha":"host2"}))) # print(host_manage("${{{haha}}}$".format(**{"haha":"host2"})))

View File

@ -49,6 +49,13 @@ class Config(object):
self.config.read(self.conf_path, encoding='utf-8') self.config.read(self.conf_path, encoding='utf-8')
return self.config['dingding'] return self.config['dingding']
def read_encryption(self):
"""
读取加密配置签名
:return:
"""
self.config.read(self.conf_path, encoding='utf-8')
return self.config['encryption']
if __name__ == '__main__': if __name__ == '__main__':
c = Config() c = Config()

View File

@ -42,4 +42,6 @@ password = 123456
charset = UTF-8 charset = UTF-8
[dingding] [dingding]
webhook = https://oapi.dingtalk.com/robot/send?access_token=2e7987d965c9ce0fd3d7a703d9653f9f12237dd9b5f07a21a2e1cb84cc2bbb7e webhook = https://oapi.dingtalk.com/robot/send?access_token=2e7987d965c9ce0fd3d7a703d9653f9f12237dd9b5f07a21a2e1cb84cc2bbb7e
secret = SEC8867b2fed306092b1005e4c85ca62de803fb3ae2fb34ee6ab6ea90a595aa57a0 secret = SEC8867b2fed306092b1005e4c85ca62de803fb3ae2fb34ee6ab6ea90a595aa57a0
[encryption]
sign = 12345hhhhhh

BIN
pic/img_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
pic/img_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -1,5 +1,5 @@
allure-pytest==2.9.43 allure-pytest==2.9.45
allure-python-commons==2.9.43 allure-python-commons==2.9.45
asgiref==3.4.1 asgiref==3.4.1
atomicwrites==1.4.0 atomicwrites==1.4.0
attrs==21.2.0 attrs==21.2.0
@ -8,11 +8,15 @@ Brotli==1.0.9
certifi==2021.5.30 certifi==2021.5.30
cffi==1.15.0 cffi==1.15.0
chardet==4.0.0 chardet==4.0.0
charset-normalizer==2.0.12
click==8.0.3 click==8.0.3
colorama==0.4.4 colorama==0.4.4
coverage==5.5 coverage==5.5
cryptography==3.4.8 cryptography==36.0.2
Deprecated==1.2.13
DingtalkChatbot==1.5.3 DingtalkChatbot==1.5.3
execnet==1.9.0
Faker==13.3.2
Flask==2.0.2 Flask==2.0.2
h11==0.12.0 h11==0.12.0
h2==4.1.0 h2==4.1.0
@ -26,7 +30,7 @@ jsonpath==0.82
kaitaistruct==0.9 kaitaistruct==0.9
ldap3==2.9.1 ldap3==2.9.1
MarkupSafe==2.0.1 MarkupSafe==2.0.1
mitmproxy==7.0.4 mitmproxy==8.0.0
msgpack==1.0.3 msgpack==1.0.3
packaging==20.9 packaging==20.9
passlib==1.7.4 passlib==1.7.4
@ -38,14 +42,18 @@ pyasn1==0.4.8
pycparser==2.21 pycparser==2.21
pydivert==2.1.0 pydivert==2.1.0
PyMySQL==1.0.2 PyMySQL==1.0.2
pyOpenSSL==20.0.1 pyOpenSSL==22.0.0
pyparsing==2.4.7 pyparsing==2.4.7
pyperclip==1.8.2 pyperclip==1.8.2
pytest==6.2.4 pytest==7.1.1
pytest-forked==1.4.0
pytest-ordering==0.6 pytest-ordering==0.6
pytest-rerunfailures==10.0 pytest-rerunfailures==10.0
pytest-xdist==2.5.0
python-dateutil==2.8.2
PyYAML==5.4.1 PyYAML==5.4.1
requests==2.25.1 redis==4.1.4
requests==2.27.1
requests-toolbelt==0.9.1 requests-toolbelt==0.9.1
ruamel.yaml==0.17.16 ruamel.yaml==0.17.16
ruamel.yaml.clib==0.2.6 ruamel.yaml.clib==0.2.6
@ -53,11 +61,12 @@ simplejson==3.17.2
six==1.16.0 six==1.16.0
sortedcontainers==2.4.0 sortedcontainers==2.4.0
toml==0.10.2 toml==0.10.2
tomli==2.0.1
tornado==6.1 tornado==6.1
urllib3==1.26.5 urllib3==1.26.5
urwid==2.1.2 urwid==2.1.2
Werkzeug==2.0.2 Werkzeug==2.0.2
wrapt==1.13.3
wsproto==1.0.0 wsproto==1.0.0
xmltodict==0.13.0
zstandard==0.15.2 zstandard==0.15.2
faker==13.3.2
pytest-xdist==2.5.0

View File

@ -7,13 +7,13 @@ import pytest
from common.checkResult import asserting from common.checkResult import asserting
from util.tools.log import Log from util.tools.log import Log
from util.tools.readYamlFile import ini_allyaml,readRedisData from util.tools.readYamlFile import ini_allyaml, readRedisData
from common.basePage import apisend from common.basePage import apisend
from util.tools.iniRequests import relevance from util.tools.iniRequests import relevance
from util.tools.iniHeaders import iniheaders from util.tools.iniHeaders import iniheaders
from util.tools.requestsTearDown import caseTearDown from util.tools.requestsTearDown import caseTearDown
from util.tools.readYamlFile import readRedisData from util.tools.readYamlFile import readRedisData
from util.tools.encryption import Encryption
Log() Log()
__all__ = [ __all__ = [
@ -28,4 +28,5 @@ __all__ = [
'relevance', 'relevance',
'iniheaders', 'iniheaders',
'caseTearDown', 'caseTearDown',
] 'Encryption',
]

View File

@ -38,6 +38,7 @@ from util.tools.iniRequests import relevance
from util.tools.iniHeaders import iniheaders from util.tools.iniHeaders import iniheaders
from util.tools.requestsTearDown import caseTearDown from util.tools.requestsTearDown import caseTearDown
from util.tools.readYamlFile import readRedisData from util.tools.readYamlFile import readRedisData
from util.tools.encryption import Encryption
Log() Log()
@ -53,6 +54,7 @@ __all__ = [
'relevance', 'relevance',
'iniheaders', 'iniheaders',
'caseTearDown', 'caseTearDown',
'Encryption',
]""") ]""")
if "conftest.py" not in os.listdir(casepath): if "conftest.py" not in os.listdir(casepath):
with open(casepath + "/" + r"{}".format("conftest.py"), 'w', encoding='utf-8') as f: with open(casepath + "/" + r"{}".format("conftest.py"), 'w', encoding='utf-8') as f:

View File

@ -16,7 +16,8 @@ from util.tools.log import Log
from util.tools.mkDir import mk_dir from util.tools.mkDir import mk_dir
Log() Log()
Host = host_manage("${HB}$") hostname = "HB"
Host = host_manage(f"${{{hostname}}}$")
class Counter: class Counter:
@ -60,6 +61,7 @@ class Counter:
# detail["order"] = self.num # detail["order"] = self.num
detail["case"] = [case] detail["case"] = [case]
detail["file"] = False detail["file"] = False
detail["encryption"] = False
# case 数据 # case 数据
case["info"] = name case["info"] = name

View File

@ -44,6 +44,9 @@ class Test_{filename}(object):""")
@pytest.mark.parametrize('casedata', readRedisData("{item}")["case"], @pytest.mark.parametrize('casedata', readRedisData("{item}")["case"],
ids=[i["info"] for i in readRedisData("{item}")["case"]]) ids=[i["info"] for i in readRedisData("{item}")["case"]])
@pytest.mark.flaky(reruns=1, reruns_delay=1)""") @pytest.mark.flaky(reruns=1, reruns_delay=1)""")
if filedata[item]["encryption"]:
f.write(f"""
@Encryption()""")
if filedata[item]["token"] and filedata[item]["token"] == "Cookies": if filedata[item]["token"] and filedata[item]["token"] == "Cookies":
# 判断是否需要token 默认类型是 Authorization # 判断是否需要token 默认类型是 Authorization
f.write(f""" f.write(f"""

66
util/tools/encryption.py Normal file
View File

@ -0,0 +1,66 @@
# coding:utf-8
"""
@author: jing
@contact: 529548204@qq.com
@file: encryption.py
@time: 2022/5/17 14:22
"""
import hashlib
from functools import wraps
from config.confManage import encryption_manage
class Encryption(object):
def __init__(self, api_key=encryption_manage("${sign}$")):
self.api_key = api_key
self.data = {}
def __call__(self, func):
@wraps(func)
def wrapped_function(*args, **kwargs):
# 打开logfile并写入
# print(kwargs["casedata"]["data"])
self.data = kwargs["casedata"]["data"]
kwargs["casedata"]["data"]["param"] = self.custom_encryption()
return func(*args, **kwargs)
return wrapped_function
def custom_encryption(self):
"""
自定义加密规则 (规则需要自己写以下为例子)
返回结果是加密后全部参数 可以是"a=1&b=2"也可以是{"a":1,"b":2}
:return:
"""
if "sign" in self.data["param"]:
# 弹出sign
self.data["param"].pop('sign')
dataList = []
for key in sorted(self.data["param"]):
if self.data["param"][key]:
dataList.append("%s=%s" % (key, self.data["param"][key]))
lastdata = "&".join(dataList)
data = lastdata + "&key=" + self.api_key.strip()
md = hashlib.md5()
md.update(data.encode("utf-8"))
signature = md.hexdigest().upper()
self.data["param"]['sign'] = signature
dictxml = self.data["param"]
# dict转成xml
return dictxml
if __name__ == '__main__':
api_keys = "8ViypSUsKSbK26g7HDsKtZSCTUsTtEvB"
da = {'mch_id': 160467, 'nonce_str': 'eh0k52jvvvs51dbxr2pd', 'out_trade_no': 10002112123231, 'sign': '{{sign}}'}
# s = logit(api_keys,da)
# s.send_post()
# s.data_processing()
# s.md5()
# print(s.rewrite_xml())
# s.rewrite_xml()