From 2091705ac47513aa75bd4028c4cb7e697b8e4621 Mon Sep 17 00:00:00 2001 From: zy7y <13271962515@163.com> Date: Sat, 21 Nov 2020 17:40:59 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9Econfig.yaml=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E8=AF=B7=E6=B1=82=E5=A4=B4=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=8C=E6=95=B4=E5=90=88=E6=96=87=E4=BB=B6=E8=AF=BB=E5=8F=96?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 +++- api/base_requests.py | 2 +- config/config.yaml | 9 ++++- data/case_data.xlsx | Bin 29184 -> 29184 bytes test/test_api.py | 92 ++++++++++++++---------------------------- tools/__init__.py | 1 + tools/data_process.py | 29 ++++++------- tools/read_file.py | 65 +++++++++++++++++++++++++++++ 8 files changed, 123 insertions(+), 83 deletions(-) create mode 100644 tools/read_file.py 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 a20a03152c476c463065e9a362e4273049572766..77f7b67946c8c6440b7a4867122b81bc7d98ca60 100644 GIT binary patch delta 2038 zcma)7T}&KR6h3!$W_O0a{efk6DL*Z(v=k~}G1P_@ni`C20)-N_Ap(sBwhd5PX%tPm zY)p)e)lg0oH8Cb_wDG}2VN4TW0F4@7Y|=i^7c|xolfD>j()g1W{O;`RWr;7&=H7F^ zbIv{AIp^M)on95wtK#!9yOCU;qJo-s>!(k$hBRylWCG5*br@p-F+ z%dBP|23~-D7VDzC7=LG#@<(PFFT>|2_*6ruSutmSUWXxyI32X(Aq2j^j`<5LND2AGDe~$*ILAPg0=|pp^(j?Pz8-CoALE;t zn!_DAwIJ{W3Se4CS3x5^1L64`-|`ZU!O#BRn9sVYcH{ zSsztHo#&H|G$6~lh|D$Z&Uf7TQW$v`H$t1i$BLz2I#SE?#g!uD+$?@qh(bFsr}2Ta61}xk?BUN2*ji;De}CXf@uO3C@TAacdij;Mhg!q1bGH4qChGZ8TO}tC z7l7zkYMP<#^d42y2as1FuR?wd`3Y_3Kf9|$wPW}Es*4U@ zdv>q(^fBkcpe~%2=ikv%vR4l&k2KQh8Hy@oP=ZPo@>3_tzGXa3*kTGLXO4JPmke zduo6_sCsIE{wVEmJyUX{q<_TQ=!Z;DjY5)sr$Ul`mqOXfCB3YDgWiv*L8?`rx%7UQ zZ+FI)q5u{Ns`Fd}ZoTyiNy0q}|1R z=r*q5G%m{uU8D&*kKd~p6Y6(Ri_mr04QfYLtr+sD0Jl%xKfEq5VP(FLmX}%cB7*j2 zpLIO)?OqTs)9a!DzS2K4`0_wBDtkf5*9Wz5rHTKS>jPC`sq-O}d^vo`I>5^Vr&0^z z+YOW2z%uGuhhR@)b(~IMbsD~tbP{qLJLJyez%Rl> zJ~H(Bx~y-+v@y72@R0b$d2{ef|7>zsZ9dFvC{gxWej$P$4nsyTiZSG;a?R;Zek%gk zwB2o2-|lrPPF>LW!_jIk8`;N8W3kz!&MTuc#x%{{xXWk8x@N~G{qhfShv4u?!ue*r MRlu)(equ@d3&O2z7ytkO delta 1996 zcma)7YiJx*6h3z{vpciT%x1gKY|`|RHcfStwi=;Lx>9Xq#l|Fpnn*VRDK(mqiWE}8 zjeiP0DD5606|7J}DWz1?QK9`|TM+*%r6Lq;u?R&g8Yt8sF@AS;_jZS>c!#-XzI(oV zzH{zlcmAfBzbQVO)_< zyfZ#J)Jt&*|HnA*_b>6QW-EVU#`$eC!`G|~@3Z2(WVP~dW{L*_an4xV_$t2lSSj|w zcL?|!E5V^af-hk1S&VlE;?785w~-FhCc3lIOmx>x0o|q5*O5>=XWI+G zg&^s9*Bj;=@P?`DU*_7)c@gGE(Pz0gBAM1dGGo#U=6Nt}`CYM7BI6D=yL}r^+DV?U zd*D81=S7+?+sSnMeQ8;4S}Fd+PKpRyu~J$tshj%e_>fKC_6j@;+X)Vy}f;D2}zwB%itLG*TW@?3mg@bOhMzEOJl#amG)l=yCgu=vOR zq*fZ^yZw)eTTcJLOG3-<<5N4AozsOYn%KgZcee3~T_L+fhiHy^X$#)1w1zhG?}cvX zhh1N2h9J@TjB{l7Y%teBN8vk89rOY0N!X8IKZZR;YdAa7E;^j;BR}iD#3Mu@;?!g( zKlQAsWuJGRnAC+c{@f9*97e&*6d;XsdeS|aRb)_+Rwd^Htgoa2 z`KVe+1M(xZRxzdgI)c8e(1+n7t*<$Ku6(CLQhtL%jrICG@;;LC8x^xr-iK>Cj7weB z^TQVL^8kG*?{@{el~W3CR;W?X?-BHS1pS_R{T@Nn6?~u;({%M71S(5;6FVBEymB@Q znjS&Z73@(=Sz%NmSz&JtS+GUiscO9z1fRz3sw1fAAWGZST*T!od%MeSipVhN z$uQ`N6!b(2x{(HIk%I0X$PB;789j(oc8%VpS$YGH>}S<8pvCA*_%$kkjXyHkwo(ph zHAN7UP)`Jp-~T>18S9*gVPooT1x*9^N|bo9)ESn1Le5Q?CHU7;yD+(RVv1KL2E|%u zaPqv5dk$Qz?BBTlFU$;qD=)CTwi4qXi)_3xye)EUarjelY(lS0CHx<1Cw^L38g+70 xZ)tq?l}u&&j6uI-Z;$cf^iXBCP?>qnClACr!Qajlov&wh2&XW6N$~ZV!rxGsU^V~% 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() +