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
|
||||
2. 安装Java与allure,https://www.cnblogs.com/zy7y/p/13403699.html
|
||||
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文件配置收件人邮箱,授权码,发件人邮箱
|
||||
6. 运行/test/test_api.py 文件
|
||||
#### 运行测试前修改
|
||||
|
@ -94,7 +94,6 @@
|
|||
[](https://imgchr.com/i/B21GUx)
|
||||
#### 用例说明文档
|
||||

|
||||
[点击前往用例说明文档Markdown版](apiAutoTest用例说明.md)
|
||||
|
||||
#### 使用说明
|
||||
|
||||
|
@ -115,9 +114,14 @@ 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路径参数提取,更新用例填写说明文档
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@ import requests
|
|||
|
||||
from tools import convert_json
|
||||
from tools.data_process import DataProcess
|
||||
from tools.read_data import ReadData
|
||||
|
||||
|
||||
class BaseRequest(object):
|
||||
|
@ -42,6 +41,7 @@ class BaseRequest(object):
|
|||
files = DataProcess.handler_files(file_obj)
|
||||
|
||||
if parametric_key == 'params':
|
||||
logger.info(f'{method, url, data, header}')
|
||||
res = session.request(method=method, url=url, params=data, headers=header)
|
||||
elif parametric_key == 'data':
|
||||
res = session.request(method=method, url=url, data=data, files=files, headers=header)
|
||||
|
|
|
@ -4,9 +4,15 @@ server:
|
|||
# https://space.bilibili.com/283273603 演示项目后端服务来自
|
||||
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提取规则设置
|
||||
response_reg:
|
||||
expr:
|
||||
# 提取token的jsonpath表达式
|
||||
token: $.data.token
|
||||
# 提取实际响应的断言数据jsonpath表达式,与excel中预期结果的数据进行比对用
|
||||
|
@ -16,7 +22,6 @@ file_path:
|
|||
case_data: ../data/case_data.xlsx
|
||||
report_data: ../report/data/
|
||||
report_generate: ../report/html/
|
||||
report_zip: ../report/html/apiAutoTestReport.zip
|
||||
log_path: ../log/运行日志{time}.log
|
||||
|
||||
email:
|
||||
|
|
Binary file not shown.
|
@ -7,26 +7,19 @@
|
|||
@ide: PyCharm
|
||||
@time: 2020/7/31
|
||||
"""
|
||||
import json
|
||||
import jsonpath
|
||||
from loguru import logger
|
||||
import pytest
|
||||
import allure
|
||||
from api.base_requests import BaseRequest
|
||||
from tools import *
|
||||
from tools.data_process import DataProcess
|
||||
from tools.read_config import ReadConfig
|
||||
from tools.read_data import ReadData
|
||||
from tools.read_file import ReadFile
|
||||
|
||||
# 读取配置文件 对象
|
||||
rc = ReadConfig()
|
||||
base_url = rc.read_serve_config('dev')
|
||||
token_reg, res_reg = rc.read_response_reg()
|
||||
report_data = rc.read_file_path('report_data')
|
||||
report_generate = rc.read_file_path('report_generate')
|
||||
log_path = rc.read_file_path('log_path')
|
||||
email_setting = rc.read_email_setting()
|
||||
base_url = ReadFile.read_config('$.server.dev')
|
||||
res_reg = ReadFile.read_config('$.expr.response')
|
||||
report_data = ReadFile.read_config('$.file_path.report_data')
|
||||
report_generate = ReadFile.read_config('$.file_path.report_generate')
|
||||
log_path = ReadFile.read_config('$.file_path.log_path')
|
||||
# 读取excel数据对象
|
||||
data_list = ReadData.get_data()
|
||||
data_list = ReadFile.read_testcase()
|
||||
# 请求对象
|
||||
br = BaseRequest()
|
||||
logger.info(f'配置文件/excel数据/对象实例化,等前置条件处理完毕\n\n')
|
||||
|
@ -53,55 +46,30 @@ class TestApiAuto(object):
|
|||
'data,expect', data_list)
|
||||
def test_main(self, case_number, case_title, path, is_token, method, parametric_key, file_obj,
|
||||
data, expect):
|
||||
|
||||
# 感谢:https://www.cnblogs.com/yoyoketang/p/13386145.html,提供动态添加标题的实例代码
|
||||
# 动态添加标题
|
||||
allure.dynamic.title(case_title)
|
||||
|
||||
logger.debug(f'⬇️⬇️⬇️...执行用例编号:{case_number}...⬇️⬇️⬇️️')
|
||||
with allure.step("处理相关数据依赖,header"):
|
||||
|
||||
header = DataProcess.header
|
||||
allure.attach(json.dumps(header, ensure_ascii=False, indent=4), "请求头", allure.attachment_type.TEXT)
|
||||
allure_title(case_title)
|
||||
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.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)
|
||||
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.attach(json.dumps(res, ensure_ascii=False, indent=4), "实际响应", allure.attachment_type.TEXT)
|
||||
|
||||
with allure.step("将响应结果的内容写入实际响应字典中"):
|
||||
allure_step('实际响应结果', res)
|
||||
DataProcess.save_response(case_number, res)
|
||||
allure.attach(json.dumps(DataProcess.response_dict, ensure_ascii=False, indent=4), "实际响应字典", allure.attachment_type.TEXT)
|
||||
|
||||
allure_step('响应结果写入字典', DataProcess.response_dict)
|
||||
# 写token的接口必须是要正确无误能返回token的
|
||||
if is_token == '写':
|
||||
with allure.step("从登录后的响应中提取token到header中"):
|
||||
DataProcess.handle_header(is_token, res, token_reg)
|
||||
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))
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ from loguru import logger
|
|||
|
||||
def extractor(obj: dict, expr: str = '.') -> object:
|
||||
"""
|
||||
根据表达式提取字典中的value,表达式, . 提取字典所有内容, $.case 提取一级字典case, $.case.data 提取case字典下的data
|
||||
:param obj :json/dict类型数据
|
||||
:param expr: 表达式, . 提取字典所有内容, $.case 提取一级字典case, $.case.data 提取case字典下的data
|
||||
$.0.1 提取字典中的第一个列表中的第二个的值
|
||||
|
|
|
@ -8,12 +8,16 @@
|
|||
@time: 2020/11/18
|
||||
"""
|
||||
from tools import *
|
||||
from tools.read_file import ReadFile
|
||||
|
||||
|
||||
class DataProcess:
|
||||
response_dict = {}
|
||||
header = {}
|
||||
null_header = {}
|
||||
header = ReadFile.read_config('$.request_headers')
|
||||
|
||||
logger.error(header)
|
||||
|
||||
have_token = header.copy()
|
||||
|
||||
@classmethod
|
||||
def save_response(cls, key: str, value: object) -> None:
|
||||
|
@ -36,13 +40,13 @@ class DataProcess:
|
|||
return rep_expr(path_str, cls.response_dict)
|
||||
|
||||
@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
|
||||
def handle_header(cls, token: str) -> dict:
|
||||
"""处理header
|
||||
:param token: 写: 写入token到header中, 读: 使用带token的header, 空:使用不带token的header
|
||||
return
|
||||
"""
|
||||
if token == '读':
|
||||
return cls.have_token
|
||||
else:
|
||||
return cls.header
|
||||
|
||||
|
@ -52,7 +56,6 @@ class DataProcess:
|
|||
:param file_obj: 上传文件使用,格式:接口中文件参数的名称:"文件路径地址"/["文件地址1", "文件地址2"]
|
||||
实例- 单个文件: &file&D:
|
||||
"""
|
||||
# todo 待完成
|
||||
if file_obj == '':
|
||||
return None
|
||||
for k, v in convert_json(file_obj).items():
|
||||
|
@ -78,9 +81,3 @@ class DataProcess:
|
|||
variable = convert_json(data)
|
||||
logger.info(f'最终的请求数据如下: {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