新增config.yaml设置初始请求头功能,整合文件读取文件

This commit is contained in:
zy7y 2020-11-21 17:40:59 +08:00
parent 84faeb0ce9
commit 2091705ac4
8 changed files with 123 additions and 83 deletions

View File

@ -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与allurehttps://www.cnblogs.com/zy7y/p/13403699.html 2. 安装Java与allurehttps://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

View File

@ -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)

View File

@ -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.

View File

@ -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__':

View File

@ -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 提取字典中的第一个列表中的第二个的值

View File

@ -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"]}"""))

65
tools/read_file.py Normal file
View File

@ -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 1534 后续所有关于文件读取的方法全部收纳与此
"""
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()