diff --git a/README.md b/README.md index 05eccb6..8b20b9e 100644 --- a/README.md +++ b/README.md @@ -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 @@ [![B21GUx.png](https://s1.ax1x.com/2020/11/05/B21GUx.png)](https://imgchr.com/i/B21GUx) #### 用例说明文档 ![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/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 diff --git a/api/base_requests.py b/api/base_requests.py index 6e91127..0efd363 100644 --- a/api/base_requests.py +++ b/api/base_requests.py @@ -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) diff --git a/config/config.yaml b/config/config.yaml index 39a2060..60214ee 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -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: diff --git a/data/case_data.xlsx b/data/case_data.xlsx index a20a031..77f7b67 100644 Binary files a/data/case_data.xlsx and b/data/case_data.xlsx differ diff --git a/test/test_api.py b/test/test_api.py index c22022b..ad82e67 100644 --- a/test/test_api.py +++ b/test/test_api.py @@ -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,56 +46,31 @@ 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) - path = DataProcess.handle_path(path) + 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_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) - 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 + assert really == expect if __name__ == '__main__': diff --git a/tools/__init__.py b/tools/__init__.py index c5dc4b6..cda7e82 100644 --- a/tools/__init__.py +++ b/tools/__init__.py @@ -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 提取字典中的第一个列表中的第二个的值 diff --git a/tools/data_process.py b/tools/data_process.py index 0c1fe02..9898d9c 100644 --- a/tools/data_process.py +++ b/tools/data_process.py @@ -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"]}""")) \ No newline at end of file diff --git a/tools/read_file.py b/tools/read_file.py new file mode 100644 index 0000000..43efc28 --- /dev/null +++ b/tools/read_file.py @@ -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() +