forked from DxvLwRYF/apiAutoTest
新增config.yaml设置初始请求头功能,整合文件读取文件
This commit is contained in:
parent
84faeb0ce9
commit
2091705ac4
|
@ -85,7 +85,7 @@
|
||||||
1. git clone https://gitee.com/zy7y/apiAutoTest.git / https://github.com/zy7y/apiAutoTest.git
|
1. git clone https://gitee.com/zy7y/apiAutoTest.git / https://github.com/zy7y/apiAutoTest.git
|
||||||
2. 安装Java与allure,https://www.cnblogs.com/zy7y/p/13403699.html
|
2. 安装Java与allure,https://www.cnblogs.com/zy7y/p/13403699.html
|
||||||
3. 使用pycharm打开项目使用Terminal 输入 python3 -m venv venv 新建虚拟环境 (可选)
|
3. 使用pycharm打开项目使用Terminal 输入 python3 -m venv venv 新建虚拟环境 (可选)
|
||||||
4. 执行pip install -r requirements.txt 安装依赖库(若下载超时:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r pip install -i https://pypi.tuna.tsinghua.edu.cn/simple some-package)
|
4. 执行pip install -r requirements.txt 安装依赖库(若下载超时:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt)
|
||||||
5. 修改config.ymal文件中email文件配置收件人邮箱,授权码,发件人邮箱
|
5. 修改config.ymal文件中email文件配置收件人邮箱,授权码,发件人邮箱
|
||||||
6. 运行/test/test_api.py 文件
|
6. 运行/test/test_api.py 文件
|
||||||
#### 运行测试前修改
|
#### 运行测试前修改
|
||||||
|
@ -94,7 +94,6 @@
|
||||||
[![B21GUx.png](https://s1.ax1x.com/2020/11/05/B21GUx.png)](https://imgchr.com/i/B21GUx)
|
[![B21GUx.png](https://s1.ax1x.com/2020/11/05/B21GUx.png)](https://imgchr.com/i/B21GUx)
|
||||||
#### 用例说明文档
|
#### 用例说明文档
|
||||||
![case_data.xlsx用例说明文档](./image/用例说明文档.png)
|
![case_data.xlsx用例说明文档](./image/用例说明文档.png)
|
||||||
[点击前往用例说明文档Markdown版](apiAutoTest用例说明.md)
|
|
||||||
|
|
||||||
#### 使用说明
|
#### 使用说明
|
||||||
|
|
||||||
|
@ -115,9 +114,14 @@ https://www.bilibili.com/video/BV1EE411B7SU?p=10
|
||||||
|
|
||||||
#### 更新
|
#### 更新
|
||||||
2020/08/08 增加实际响应存储数据的方法,并在字典可以处理依赖见tools/svae_response.py
|
2020/08/08 增加实际响应存储数据的方法,并在字典可以处理依赖见tools/svae_response.py
|
||||||
|
|
||||||
2020/08/09 实现多文件上传,接口中Path参数依赖处理
|
2020/08/09 实现多文件上传,接口中Path参数依赖处理
|
||||||
|
|
||||||
2020/11/18 使用re库解决当请求参数层级结构多出现无法提取的bug,减少冗余代码,优化path路径参数提取,更新用例填写说明文档
|
2020/11/18 使用re库解决当请求参数层级结构多出现无法提取的bug,减少冗余代码,优化path路径参数提取,更新用例填写说明文档
|
||||||
|
|
||||||
|
2020/11/21 更新用例文档,合并文件对象,文件地址,优化文件上传处理方式
|
||||||
|
|
||||||
|
2020/11/21 config.yaml文件中新增request_headers 选项,默认header在此设置,优化test_api.py文件,整合read_file.py
|
||||||
#### 博客园首发
|
#### 博客园首发
|
||||||
https://www.cnblogs.com/zy7y/p/13426816.html
|
https://www.cnblogs.com/zy7y/p/13426816.html
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ import requests
|
||||||
|
|
||||||
from tools import convert_json
|
from tools import convert_json
|
||||||
from tools.data_process import DataProcess
|
from tools.data_process import DataProcess
|
||||||
from tools.read_data import ReadData
|
|
||||||
|
|
||||||
|
|
||||||
class BaseRequest(object):
|
class BaseRequest(object):
|
||||||
|
@ -42,6 +41,7 @@ class BaseRequest(object):
|
||||||
files = DataProcess.handler_files(file_obj)
|
files = DataProcess.handler_files(file_obj)
|
||||||
|
|
||||||
if parametric_key == 'params':
|
if parametric_key == 'params':
|
||||||
|
logger.info(f'{method, url, data, header}')
|
||||||
res = session.request(method=method, url=url, params=data, headers=header)
|
res = session.request(method=method, url=url, params=data, headers=header)
|
||||||
elif parametric_key == 'data':
|
elif parametric_key == 'data':
|
||||||
res = session.request(method=method, url=url, data=data, files=files, headers=header)
|
res = session.request(method=method, url=url, data=data, files=files, headers=header)
|
||||||
|
|
|
@ -4,9 +4,15 @@ server:
|
||||||
# https://space.bilibili.com/283273603 演示项目后端服务来自
|
# https://space.bilibili.com/283273603 演示项目后端服务来自
|
||||||
dev: http://www.ysqorz.top:8888/api/private/v1/
|
dev: http://www.ysqorz.top:8888/api/private/v1/
|
||||||
|
|
||||||
|
# 基准的请求头信息
|
||||||
|
request_headers:
|
||||||
|
Accept-Encoding: gzip, deflate
|
||||||
|
Accept-Language: zh-CN,zh;q=0.9
|
||||||
|
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36
|
||||||
|
|
||||||
|
|
||||||
# 实际响应jsonpath提取规则设置
|
# 实际响应jsonpath提取规则设置
|
||||||
response_reg:
|
expr:
|
||||||
# 提取token的jsonpath表达式
|
# 提取token的jsonpath表达式
|
||||||
token: $.data.token
|
token: $.data.token
|
||||||
# 提取实际响应的断言数据jsonpath表达式,与excel中预期结果的数据进行比对用
|
# 提取实际响应的断言数据jsonpath表达式,与excel中预期结果的数据进行比对用
|
||||||
|
@ -16,7 +22,6 @@ file_path:
|
||||||
case_data: ../data/case_data.xlsx
|
case_data: ../data/case_data.xlsx
|
||||||
report_data: ../report/data/
|
report_data: ../report/data/
|
||||||
report_generate: ../report/html/
|
report_generate: ../report/html/
|
||||||
report_zip: ../report/html/apiAutoTestReport.zip
|
|
||||||
log_path: ../log/运行日志{time}.log
|
log_path: ../log/运行日志{time}.log
|
||||||
|
|
||||||
email:
|
email:
|
||||||
|
|
Binary file not shown.
|
@ -7,26 +7,19 @@
|
||||||
@ide: PyCharm
|
@ide: PyCharm
|
||||||
@time: 2020/7/31
|
@time: 2020/7/31
|
||||||
"""
|
"""
|
||||||
import json
|
|
||||||
import jsonpath
|
|
||||||
from loguru import logger
|
|
||||||
import pytest
|
import pytest
|
||||||
import allure
|
|
||||||
from api.base_requests import BaseRequest
|
from api.base_requests import BaseRequest
|
||||||
|
from tools import *
|
||||||
from tools.data_process import DataProcess
|
from tools.data_process import DataProcess
|
||||||
from tools.read_config import ReadConfig
|
from tools.read_file import ReadFile
|
||||||
from tools.read_data import ReadData
|
|
||||||
|
|
||||||
# 读取配置文件 对象
|
base_url = ReadFile.read_config('$.server.dev')
|
||||||
rc = ReadConfig()
|
res_reg = ReadFile.read_config('$.expr.response')
|
||||||
base_url = rc.read_serve_config('dev')
|
report_data = ReadFile.read_config('$.file_path.report_data')
|
||||||
token_reg, res_reg = rc.read_response_reg()
|
report_generate = ReadFile.read_config('$.file_path.report_generate')
|
||||||
report_data = rc.read_file_path('report_data')
|
log_path = ReadFile.read_config('$.file_path.log_path')
|
||||||
report_generate = rc.read_file_path('report_generate')
|
|
||||||
log_path = rc.read_file_path('log_path')
|
|
||||||
email_setting = rc.read_email_setting()
|
|
||||||
# 读取excel数据对象
|
# 读取excel数据对象
|
||||||
data_list = ReadData.get_data()
|
data_list = ReadFile.read_testcase()
|
||||||
# 请求对象
|
# 请求对象
|
||||||
br = BaseRequest()
|
br = BaseRequest()
|
||||||
logger.info(f'配置文件/excel数据/对象实例化,等前置条件处理完毕\n\n')
|
logger.info(f'配置文件/excel数据/对象实例化,等前置条件处理完毕\n\n')
|
||||||
|
@ -53,56 +46,31 @@ class TestApiAuto(object):
|
||||||
'data,expect', data_list)
|
'data,expect', data_list)
|
||||||
def test_main(self, case_number, case_title, path, is_token, method, parametric_key, file_obj,
|
def test_main(self, case_number, case_title, path, is_token, method, parametric_key, file_obj,
|
||||||
data, expect):
|
data, expect):
|
||||||
|
|
||||||
# 感谢:https://www.cnblogs.com/yoyoketang/p/13386145.html,提供动态添加标题的实例代码
|
|
||||||
# 动态添加标题
|
|
||||||
allure.dynamic.title(case_title)
|
|
||||||
|
|
||||||
logger.debug(f'⬇️⬇️⬇️...执行用例编号:{case_number}...⬇️⬇️⬇️️')
|
logger.debug(f'⬇️⬇️⬇️...执行用例编号:{case_number}...⬇️⬇️⬇️️')
|
||||||
with allure.step("处理相关数据依赖,header"):
|
|
||||||
|
|
||||||
header = DataProcess.header
|
allure_title(case_title)
|
||||||
allure.attach(json.dumps(header, ensure_ascii=False, indent=4), "请求头", allure.attachment_type.TEXT)
|
path = DataProcess.handle_path(path)
|
||||||
path = DataProcess.handle_path(path)
|
allure_step('请求地址', base_url + path)
|
||||||
|
allure_step('请求方式', method)
|
||||||
|
header = DataProcess.handle_header(is_token)
|
||||||
|
allure_step('请求头', header)
|
||||||
|
data = DataProcess.handle_data(data)
|
||||||
|
allure_step('请求数据', data)
|
||||||
|
res = br.api_send(method=method, url=base_url + path, parametric_key=parametric_key, file_obj=file_obj,
|
||||||
|
data=data, header=header)
|
||||||
|
allure_step('实际响应结果', res)
|
||||||
|
DataProcess.save_response(case_number, res)
|
||||||
|
allure_step('响应结果写入字典', DataProcess.response_dict)
|
||||||
|
# 写token的接口必须是要正确无误能返回token的
|
||||||
|
if is_token == '写':
|
||||||
|
DataProcess.have_token['Authorization'] = extractor(res, ReadFile.read_config('$.expr.token'))
|
||||||
|
really = extractor(res, res_reg)
|
||||||
|
allure_step('根据配置文件的提取响应规则提取实际数据', really)
|
||||||
|
expect = convert_json(expect)
|
||||||
|
allure_step('处理读取出来的预期结果响应', expect)
|
||||||
|
allure_step('响应断言', (really == expect))
|
||||||
|
|
||||||
data = DataProcess.handle_data(data)
|
assert really == expect
|
||||||
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, ensure_ascii=False, indent=4), "最终请求地址", allure.attachment_type.TEXT)
|
|
||||||
res = br.api_send(method=method, url=base_url + path, parametric_key=parametric_key, file_obj=file_obj,
|
|
||||||
data=data, header=header)
|
|
||||||
allure.attach(json.dumps(res, ensure_ascii=False, indent=4), "实际响应", allure.attachment_type.TEXT)
|
|
||||||
|
|
||||||
with allure.step("将响应结果的内容写入实际响应字典中"):
|
|
||||||
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中"):
|
|
||||||
DataProcess.handle_header(is_token, res, token_reg)
|
|
||||||
|
|
||||||
with allure.step("根据配置文件的提取响应规则提取实际数据"):
|
|
||||||
really = jsonpath.jsonpath(res, res_reg)[0]
|
|
||||||
allure.attach(json.dumps(really, ensure_ascii=False, indent=4), "提取用于断言的实际响应部分数据", allure.attachment_type.TEXT)
|
|
||||||
|
|
||||||
with allure.step("处理读取出来的预期结果响应"):
|
|
||||||
# 处理预期结果数据中使用True/False/None导致的无法转换bug
|
|
||||||
if 'None' in expect:
|
|
||||||
expect = expect.replace('None', 'null')
|
|
||||||
if 'True' in expect:
|
|
||||||
expect = expect.replace('True', 'true')
|
|
||||||
if 'False' in expect:
|
|
||||||
expect = expect.replace('False', 'false')
|
|
||||||
expect = json.loads(expect)
|
|
||||||
allure.attach(json.dumps(expect, ensure_ascii=False, indent=4), "预期响应", allure.attachment_type.TEXT)
|
|
||||||
|
|
||||||
with allure.step("预期结果与实际响应进行断言操作"):
|
|
||||||
logger.info(f'完整的json响应: {res}\n需要校验的数据字典: {really} 预期校验的数据字典: {expect} \n测试结果: {really == expect}')
|
|
||||||
logger.debug(f'⬆⬆⬆...用例编号:{case_number},执行完毕,日志查看...⬆⬆⬆\n\n️')
|
|
||||||
allure.attach(json.dumps(really == expect, ensure_ascii=False, indent=4), "测试结果", allure.attachment_type.TEXT)
|
|
||||||
assert really == expect
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -18,6 +18,7 @@ from loguru import logger
|
||||||
|
|
||||||
def extractor(obj: dict, expr: str = '.') -> object:
|
def extractor(obj: dict, expr: str = '.') -> object:
|
||||||
"""
|
"""
|
||||||
|
根据表达式提取字典中的value,表达式, . 提取字典所有内容, $.case 提取一级字典case, $.case.data 提取case字典下的data
|
||||||
:param obj :json/dict类型数据
|
:param obj :json/dict类型数据
|
||||||
:param expr: 表达式, . 提取字典所有内容, $.case 提取一级字典case, $.case.data 提取case字典下的data
|
:param expr: 表达式, . 提取字典所有内容, $.case 提取一级字典case, $.case.data 提取case字典下的data
|
||||||
$.0.1 提取字典中的第一个列表中的第二个的值
|
$.0.1 提取字典中的第一个列表中的第二个的值
|
||||||
|
|
|
@ -8,12 +8,16 @@
|
||||||
@time: 2020/11/18
|
@time: 2020/11/18
|
||||||
"""
|
"""
|
||||||
from tools import *
|
from tools import *
|
||||||
|
from tools.read_file import ReadFile
|
||||||
|
|
||||||
|
|
||||||
class DataProcess:
|
class DataProcess:
|
||||||
response_dict = {}
|
response_dict = {}
|
||||||
header = {}
|
header = ReadFile.read_config('$.request_headers')
|
||||||
null_header = {}
|
|
||||||
|
logger.error(header)
|
||||||
|
|
||||||
|
have_token = header.copy()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def save_response(cls, key: str, value: object) -> None:
|
def save_response(cls, key: str, value: object) -> None:
|
||||||
|
@ -36,13 +40,13 @@ class DataProcess:
|
||||||
return rep_expr(path_str, cls.response_dict)
|
return rep_expr(path_str, cls.response_dict)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def handle_header(cls, is_token: str, response: dict, reg) -> dict:
|
def handle_header(cls, token: str) -> dict:
|
||||||
"""处理header"""
|
"""处理header
|
||||||
if is_token == '写':
|
:param token: 写: 写入token到header中, 读: 使用带token的header, 空:使用不带token的header
|
||||||
cls.header['Authorization'] = extractor(response, reg)
|
return
|
||||||
return cls.header
|
"""
|
||||||
elif is_token == '':
|
if token == '读':
|
||||||
return cls.null_header
|
return cls.have_token
|
||||||
else:
|
else:
|
||||||
return cls.header
|
return cls.header
|
||||||
|
|
||||||
|
@ -52,7 +56,6 @@ class DataProcess:
|
||||||
:param file_obj: 上传文件使用,格式:接口中文件参数的名称:"文件路径地址"/["文件地址1", "文件地址2"]
|
:param file_obj: 上传文件使用,格式:接口中文件参数的名称:"文件路径地址"/["文件地址1", "文件地址2"]
|
||||||
实例- 单个文件: &file&D:
|
实例- 单个文件: &file&D:
|
||||||
"""
|
"""
|
||||||
# todo 待完成
|
|
||||||
if file_obj == '':
|
if file_obj == '':
|
||||||
return None
|
return None
|
||||||
for k, v in convert_json(file_obj).items():
|
for k, v in convert_json(file_obj).items():
|
||||||
|
@ -78,9 +81,3 @@ class DataProcess:
|
||||||
variable = convert_json(data)
|
variable = convert_json(data)
|
||||||
logger.info(f'最终的请求数据如下: {variable}')
|
logger.info(f'最终的请求数据如下: {variable}')
|
||||||
return variable
|
return variable
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
print(convert_json("""{"files":["D:\\apiAutoTest\\data\\case_data - 副本.xls", "D:\\apiAutoTest\\data\\case_data.xlsx"]}"""))
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
#!/usr/bin/env/python3
|
||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
"""
|
||||||
|
@project: apiAutoTest
|
||||||
|
@author: zy7y
|
||||||
|
@file: read_file.py
|
||||||
|
@ide: PyCharm
|
||||||
|
@time: 2020/7/31
|
||||||
|
@desc: 更新时间 2020/11/21 15:34 后续所有关于文件读取的方法全部收纳与此
|
||||||
|
"""
|
||||||
|
import yaml
|
||||||
|
import xlrd
|
||||||
|
from tools import *
|
||||||
|
|
||||||
|
|
||||||
|
class ReadFile:
|
||||||
|
config_dict = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@logger.catch
|
||||||
|
def get_config_dict(cls, config_path: str = '../config/config.yaml') -> dict:
|
||||||
|
"""读取配置文件,并且转换成字典
|
||||||
|
:param config_path: 配置文件地址, 默认使用当前项目目录下的config/config.yaml
|
||||||
|
return cls.config_dict
|
||||||
|
"""
|
||||||
|
if cls.config_dict is None:
|
||||||
|
# 指定编码格式解决,win下跑代码抛出错误
|
||||||
|
with open(config_path, 'r', encoding='utf-8') as file:
|
||||||
|
cls.config_dict = yaml.load(file.read(), Loader=yaml.FullLoader)
|
||||||
|
return cls.config_dict
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@logger.catch
|
||||||
|
def read_config(cls, expr: str = '.') -> dict:
|
||||||
|
"""默认读取config目录下的config.yaml配置文件,根据传递的expr jsonpath表达式可任意返回任何配置项
|
||||||
|
:param expr: 提取表达式, 使用jsonpath语法,默认值提取整个读取的对象
|
||||||
|
return 根据表达式返回的值
|
||||||
|
"""
|
||||||
|
return extractor(cls.get_config_dict(), expr)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@logger.catch
|
||||||
|
def read_testcase(cls):
|
||||||
|
"""
|
||||||
|
读取excel格式的测试用例
|
||||||
|
:return: data_list - pytest参数化可用的数据
|
||||||
|
"""
|
||||||
|
data_list = []
|
||||||
|
book = xlrd.open_workbook(cls.read_config('$.file_path.case_data'))
|
||||||
|
# 读取第一个sheet页
|
||||||
|
table = book.sheet_by_index(0)
|
||||||
|
for norw in range(1, table.nrows):
|
||||||
|
# 每行第4列 是否运行
|
||||||
|
if table.cell_value(norw, 3) != '否': # 每行第4列等于否将不读取内容
|
||||||
|
value = table.row_values(norw)
|
||||||
|
value.pop(3)
|
||||||
|
data_list.append(list(value))
|
||||||
|
logger.info(f'{data_list}')
|
||||||
|
return data_list
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
ReadFile.read_config()
|
||||||
|
ReadFile.read_testcase()
|
||||||
|
|
Loading…
Reference in New Issue