功能

1.实现:get/post请求(上传文件)::理论上其他delete/put等请求也实现了,支持restful接口规范
2.发送邮件
3.生成allure测试报告
4.压缩测试报告文件
5.数据依赖

运行机制

1.通过读取配置文件,获取到host地址、提取token的jsonpath表达式,提取实际响应结果用来与预期结果比对的jsonpath表达式。
2.读取excel用例文件数据,组成一个符合pytest参数化的用例数据,根据每列进行数据处理(token操作、数据依赖)
3.token,写,需要使用一个正常登录的接口,并且接口中要返回token数据,才可以提取,token,读为该请求将携带有token的header,token 无数据的将不携带token
4.数据依赖处理,从excel中读取出来的格式{“用例编号”:[“jsonpath表达式1”, “jsonpath表达式2”]},通过用例编号来获取对应case的实际响应结果(实际响应结果在发送请求后,回写到excel中),通过jsonpath表达式提取对应的依赖参数字段,以及对应的值,最终会返回一个存储该接口需要依赖数据的字典如{“userid”:500, “username”: “zy7y”},在发送请求时与请求数据进行合并,组成一个新的data放到请求中
5.每次请求完成之后将回写实际的响应结果到excel中
6.根据配置文件中配置的jsonpath表达式提取实际响应内容与excel中预期结果的数据对比
7.生成测试报告
8.压缩测试报告文件夹
9.发送邮件

已知问题

执行接口消耗时间变长,代码乱(语言学的不扎实),频繁读写excel(可考虑用字典存每个接口的实际响应,取值直接从响应字典中取出)整体代码结构优化未实现,导致最终测试时间变长,其他工具单接口测试只需要39ms,该框架中使用了101ms,考虑和频繁读写用例数据导致

环境与依赖

名称 版本 作用
python 3.7.8
pytest 6.0.1 底层单元测试框架,用来实现参数化,自动执行用例
allure-pytest 2.8.17 allure与pytest的插件可以生成allure的测试报告
jsonpath 0.82 用来进行响应断言操作
loguru 0.54 记录日志
PyYAML 5.3.1 读取yml/yaml格式的配置文件
Allure 2.13.5 要生成allure测试报告必须要在本机安装allure并配置环境变量
xlrd 1.2.0 用来读取excel中用例数据
yagmail 0.11.224 测试完成后发送邮件
requests 2.24.0 发送请求

目录结构

执行顺序

运行test_api.py -> 读取config.yaml(tools.read_config.py) -> 读取excel用例文件(tools.read_data.py) -> test_api.py实现参数化 -> 处理是否依赖数据 ->base_requests.py发送请求 -> test_api.py断言 -> read_data.py回写实际响应到用例文件中(方便根据依赖提取对应的数据)

config.ymal展示

server:test: http://127.0.0.1:8888/api/private/v1/# 实例代码使用的接口服务,已改为作者是自己的云服务器部署。(后端源码来自b站:https://www.bilibili.com/video/BV1EE411B7SU?p=10)dev: http://49.232.203.244:8888/api/private/v1/# 实际响应jsonpath提取规则设置
response_reg:# 提取token的jsonpath表达式token: $.data.token# 提取实际响应的断言数据jsonpath表达式,与excel中预期结果的数据进行比对用response: $.metafile_path:case_data: ../data/case_data.xlsxreport_data: ../report/data/report_generate: ../report/html/report_zip: ../report/html/apiAutoTestReport.ziplog_path: ../log/运行日志{time}.logemail:# 发件人邮箱user:123456.com# 发件人邮箱授权码password:ASGCSFSGS# 邮箱hosthost:smtp.163.comcontents:解压apiAutoReport.zip(接口测试报告)后,请使用已安装Live Server 插件的VsCode,打开解压目录下的index.html查看报告# 收件人邮箱addressees:["收件人邮箱1","收件人邮箱2","收件人邮箱3"]title:接口自动化测试报告(见附件)# 附件地址enclosures: ["../report/html/apiAutoTestReport.zip",]

EXcel用例展示

脚本一览

请求方法封装

#!/usr/bin/env/python3
# -*- coding:utf-8 -*-
"""
@project: apiAutoTest
@author: zy7y
@file: base_requests.py
@ide: PyCharm
@time: 2020/7/31
"""
from test import logger
import requestsclass BaseRequest(object):def __init__(self):pass# 请求def base_requests(self, method, url, parametric_key=None, data=None, file_var=None, file_path=None, header=None):""":param method: 请求方法:param url: 请求url:param parametric_key: 入参关键字, get/delete/head/options/请求使用params, post/put/patch请求可使用json(application/json)/data:param data: 参数数据,默认等于None:param file_var: 接口中接受文件的参数关键字:param file_path: 文件对象的地址, 单个文件直接放地址:/Users/zy7y/Desktop/vue.js多个文件格式:["/Users/zy7y/Desktop/vue.js","/Users/zy7y/Desktop/jenkins.war"]:param header: 请求头:return: 返回json格式的响应"""session = requests.Session()if (file_var in [None, '']) and (file_path in [None, '']):files = Noneelse:# 文件不为空的操作if file_path.startswith('[') and file_path.endswith(']'):file_path_list = eval(file_path)files = []# 多文件上传for file_path in file_path_list:files.append((file_var, (open(file_path, 'rb'))))else:# 单文件上传files = {file_var: open(file_path, 'rb')}if parametric_key == 'params':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)elif parametric_key == 'json':res = session.request(method=method, url=url, json=data, files=files, headers=header)else:raise ValueError('可选关键字为:get/delete/head/options/请求使用params, post/put/patch请求可使用json(application/json)/data')logger.info(f'请求方法:{method},请求路径:{url}, 请求参数:{data}, 请求文件:{files}, 请求头:{header})')return res.json()

读取excel用例数据

#!/usr/bin/env/python3
# -*- coding:utf-8 -*-
"""
@project: apiAutoTest
@author: zy7y
@file: read_data.py
@ide: PyCharm
@time: 2020/7/31
"""
import xlrd
from test import loggerclass ReadData(object):def __init__(self, excel_path):self.excel_file = excel_pathself.book = xlrd.open_workbook(self.excel_file)def get_data(self):""":return: data_list - pytest参数化可用的数据"""data_list = []table = self.book.sheet_by_index(0)for norw in range(1, table.nrows):# 每行第4列 是否运行if table.cell_value(norw, 3) == '否':continuevalue = table.row_values(norw)value.pop(3)# 配合将每一行转换成元组存储,迎合 pytest的参数化操作,如不需要可以注释掉 value = tuple(value)value = tuple(value)logger.info(f'{value}')data_list.append(value)return data_list

存储接口实际结果响应

#!/usr/bin/env/python3
# -*- coding:utf-8 -*-
"""
@project: apiAutoTest的副本
@author: zy7y
@file: save_response.py
@ide: PyCharm
@time: 2020/8/8
"""
import jsonimport jsonpath
from test import loggerclass SaveResponse(object):def __init__(self):self.actual_response = {}# 保存实际响应def save_actual_response(self, case_key, case_response):""":param case_key:用例编号:param case_response:对应用例编号的实际响应:return:"""self.actual_response[case_key] = case_responselogger.info(f'当前字典数据{self.actual_response}')# 读取依赖数据def read_depend_data(self, depend):""":param depend: 需要依赖数据字典{"case_001":"['jsonpaht表达式1', 'jsonpaht表达式2']"}:return:"""depend_dict = {}depend = json.loads(depend)for k, v in depend.items():# 取得依赖中对应case编号的值提取表达式try:for value in v:# value : '$.data.id'# 取得对应用例编号的实际响应结果actual = self.actual_response[k]# 返回依赖数据的keyd_k = value.split('.')[-1]# 添加到依赖数据字典并返回depend_dict[d_k] = jsonpath.jsonpath(actual, value)[0]except TypeError as e:logger.error(f'实际响应结果中无法正常使用该表达式提取到任何内容,发现异常{e}')return depend_dict

处理依赖数据逻辑

#!/usr/bin/env/python3
# -*- coding:utf-8 -*-
"""
@project: apiAutoTest
@author: zy7y
@file: data_tearing.py
@ide: PyCharm
@time: 2020/8/10
"""
import json
from json import JSONDecodeErrorimport jsonpath
from test import loggerclass TreatingData(object):"""处理hader/path路径参数/请求data依赖数据代码"""def __init__(self):self.no_token_header = {}self.token_header = {}def treating_data(self, is_token, parameters, dependent, data, save_response_dict):# 使用那个headerif is_token == '':header = self.no_token_headerelse:header = self.token_headerlogger.info(f'处理依赖前data的数据:{data}')# 处理依赖数据dataif dependent != '':dependent_data = save_response_dict.read_depend_data(dependent)logger.debug(f'依赖数据解析获得的字典{dependent_data}')if data != '':# 合并组成一个新的datadependent_data.update(json.loads(data))data = dependent_datalogger.info(f'data有数据,依赖有数据时 {data}')else:# 赋值给datadata = dependent_datalogger.info(f'data无数据,依赖有数据时 {data}')else:if data == '':data = Nonelogger.info(f'data无数据,依赖无数据时 {data}')else:data = json.loads(data)logger.info(f'data有数据,依赖无数据 {data}')# 处理路径参数Path的依赖# 传进来的参数类似 {"case_002":"$.data.id"}/item/{"case_002":"$.meta.status"},进行列表拆分path_list = parameters.split('/')# 获取列表长度迭代for i in range(len(path_list)):# 按着try:# 尝试序列化成dict: json.loads('2') 可以转换成2path_dict = json.loads(path_list[i])except JSONDecodeError as e:# 序列化失败此path_list[i]的值不变化logger.error(f'无法转换字典,进入下一个检查,本轮值不发生变化:{path_list[i]},{e}')# 跳过进入下次循环continueelse:# 解析该字典,获得用例编号,表达式logger.info(f'{path_dict}')# 处理json.loads('数字')正常序列化导致的AttributeErrortry:for k, v in path_dict.items():try:# 尝试从对应的case实际响应提取某个字段内容path_list[i] = jsonpath.jsonpath(save_response_dict.actual_response[k], v)[0]except TypeError as e:logger.error(f'无法提取,请检查响应字典中是否支持该表达式,{e}')except AttributeError as e:logger.error(f'类型错误:{type(path_list[i])},本此将不转换值 {path_list[i]},{e}')# 字典中存在有不是str的元素:使用map 转换成全字符串的列表path_list = map(str, path_list)# 将字符串列表转换成字符:500/item/200parameters_path_url = "/".join(path_list)logger.info(f'path路径参数解析依赖后的路径为{parameters_path_url}')return data, header, parameters_path_url

启动文件

#!/usr/bin/env/python3
# -*- coding:utf-8 -*-
"""
@project: apiAutoTest
@author: zy7y
@file: test_api.py
@ide: PyCharm
@time: 2020/7/31
"""
import json
import jsonpath
from test import logger
import pytest
import allure
from api.base_requests import BaseRequest
from tools.data_tearing import TreatingData
from tools.read_config import ReadConfig
from tools.read_data import ReadData
from tools.save_response import SaveResponse# 读取配置文件 对象
rc = ReadConfig()
base_url = rc.read_serve_config('dev')
token_reg, res_reg = rc.read_response_reg()
case_data_path = rc.read_file_path('case_data')
report_data = rc.read_file_path('report_data')
report_generate = rc.read_file_path('report_generate')
log_path = rc.read_file_path('log_path')
report_zip = rc.read_file_path('report_zip')
email_setting = rc.read_email_setting()
# 实例化存响应的对象
save_response_dict = SaveResponse()
# 读取excel数据对象
data_list = ReadData(case_data_path).get_data()
# 数据处理对象
treat_data = TreatingData()
# 请求对象
br = BaseRequest()
logger.info(f'配置文件/excel数据/对象实例化,等前置条件处理完毕\n\n')class TestApiAuto(object):# 启动方法def run_test(self):import os, shutilif os.path.exists('../report') and os.path.exists('../log'):shutil.rmtree(path='../report')shutil.rmtree(path='../log')# 日志存取路径logger.add(log_path, encoding='utf-8')pytest.main(args=[f'--alluredir={report_data}'])os.system(f'allure generate {report_data} -o {report_generate} --clean')logger.warning('报告已生成')@pytest.mark.parametrize('case_number,case_title,path,is_token,method,parametric_key,file_var,' 'file_path, parameters, dependent,data,expect', data_list)def test_main(self, case_number, case_title, path, is_token, method, parametric_key, file_var,file_path, parameters, dependent, data, expect):""":param case_number: 用例编号:param case_title: 用例标题:param path: 接口路径:param is_token: token操作:写入token/读取token/不携带token:param method: 请求方式:get/post/put/delete....:param parametric_key: 入参关键字:params/data/json:param file_var: 接口中接受文件对象的参数名称:param file_path: 文件路径,单文件实例:/Users/zy7y/PycharmProjects/apiAutoTest/test/__init__.py多文件实例['/Users/zy7y/PycharmProjects/apiAutoTest/test/__init__.py','/Users/zy7y/PycharmProjects/apiAutoTest/test/test_api.py']:param parameters: path参数(携带在url中的参数)依赖处理 users/:id(id携带在url中) 实例:{"case_001": '$.data.id'},解析从用例编号为case_001的实际结果响应中提取data字典里面的id的内容(假设提取出来是500), 最后请求的路径将是host + users/500:param dependent: data数据依赖,该接口需要上一个接口返回的响应中的某个字段及内容:实例{"case_001",["$.data.id","$.data.username"]}解析: 从用例case_001的实际响应结果中提取到data下面的id,与username的值(假设id值为500,username为admin),那么提取的数据依赖内容将是{"id":500, "username":"admin"}纳闷最终请求的data 将是 {"id":500, "username":"admin"} 与本身的data合并后的内容:param data: 请求数据:param expect:预期结果,最后与config/config.yaml下的response_reg->response提取出来的实际响应内容做对比,实现断言:return:"""# 感谢:https://www.cnblogs.com/yoyoketang/p/13386145.html,提供动态添加标题的实例代码# 动态添加标题allure.dynamic.title(case_title)logger.debug(f'⬇️⬇️⬇️...执行用例编号:{case_number}...⬇️⬇️⬇️️')with allure.step("处理相关数据依赖,header"):data, header, parameters_path_url = treat_data.treating_data(is_token, parameters, dependent, data, save_response_dict)with allure.step("发送请求,取得响应结果的json串"):res = br.base_requests(method=method, url=base_url + path + parameters_path_url, parametric_key=parametric_key, file_var=file_var, file_path=file_path, data=data, header=header)with allure.step("将响应结果的内容写入实际响应字典中"):save_response_dict.save_actual_response(case_key=case_number, case_response=res)# 写token的接口必须是要正确无误能返回token的if is_token == '写':with allure.step("从登录后的响应中提取token到header中"):treat_data.token_header['Authorization'] = jsonpath.jsonpath(res, token_reg)[0]with allure.step("根据配置文件的提取响应规则提取实际数据"):really = jsonpath.jsonpath(res, res_reg)[0]with allure.step("处理读取出来的预期结果响应"):expect = json.loads(expect)with allure.step("预期结果与实际响应进行断言操作"):assert really == expectlogger.info(f'完整的json响应: {res}\n需要校验的数据字典: {really} 预期校验的数据字典: {expect} \n测试结果: {really == expect}')logger.debug(f'⬆⬆⬆...用例编号:{case_number},执行完毕,日志查看...⬆⬆⬆\n\n️')if __name__ == '__main__':TestApiAuto().run_test()# 使用jenkins集成将不会使用到这两个方法(邮件发送/报告压缩zip)# from tools.zip_file import zipDir# from tools.send_email import send_email# zipDir(report_generate, report_zip)# send_email(email_setting)

运行测试

首先确保需要的环境与依赖包无问题之后,使用Pycharm打开项目,找到settings修改为unitest或者其他非pytest,具体操作如下<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ca0626aa9e8845589c21d4af249b49e6~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image)[[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6683pLbA-1660706829752)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6d661ae5598e4d3dba387750ed45bbed~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image)]](https://link.juejin.cn/?target=https%3A%2F%2Fimgchr.com%2Fi%2FB21GUx “https://imgchr.com/i/B21GUx”" style=“margin: auto” />

运行结果

致谢

jsonpath语法学习:blog.csdn.net/liuchunming…zip文件压缩:www.cnblogs.com/yhleng/p/94…欢迎交流。

源码地址

源码地址Gitee - version1.0分支: gitee.com/zy7y/apiAut…源码地址GitHub- version1.0 分支:github.com/zy7y/apiAut…

更新

tree/version1.0/")源码地址GitHub- version1.0 分支:github.com/zy7y/apiAut…

更新

2020/11/23 - 优化数据参数、路径参数依赖处理方式,现版本与之前同等环境下,测试时间提升2S介绍:www.cnblogs.com/zy7y/p/1402…

Python接口自动化测试工具(Pytest+Allure+jsonpath+xlrd+excel、支持Restful接口规范)相关推荐

  1. 【python数据驱动+接口自动化测试】pytest+allure+yaml+jenkins+git(gitlab/gitee)下的接口自动化测试实战

    大家好,我是好学的小师弟.今天和大家分享下我前段时间的工作学习心得-接口自动化测试及其全套工作流程. 注:本文的侧重点在于工作流程,代码讲解.工具安装步骤方面可能就浅尝辄止了. 目录 前言: 工作流程 ...

  2. 测试进阶必备,这5款http接口自动化测试工具简直不要太香~

    现在市场上能做接口自动化测试的工具有很多,一搜一大把,让人眼花缭乱.我们去选择对应实现方式时,不管是框架体系还是成熟稳定的工具,核心目的都是期望引入的技术能在最低投入的情况下达到最优效果. 那么我们选 ...

  3. 阿里P8:你们公司就这水平?看看这份Python接口自动化测试手册

    前阵子有幸参加了个2021英雄技术会,与会了一个阿里P8技术大佬,我兴致勃勃地把我们公司的整套测试流程展示给大佬看,并重点介绍了我司自动化测试,谁知道大佬看完后来了句:就这?就这水平?随后丢给我一份P ...

  4. Python接口自动化测试实战详解,你想要的全都有

    目录 前言 一.接口自动化测试概述 二.搭建Python接口自动化测试框架 三.常用接口自动化测试库示例 四.总结 前言 接口自动化测试是当前软件开发中最重要的环节之一,可以提高代码质量.加速开发周期 ...

  5. python自动化测试视频百度云-Python接口自动化测试 PDF 超清版

    给大家带来的一篇关于Python自动化相关的电子书资源,介绍了关于Python.接口自动化.测试方面的内容,本书是由电子工业出版社出版,格式为PDF,资源大小61.2 MB,王浩然编写,目前豆瓣.亚马 ...

  6. python自动化测试视频百度云-python接口自动化测试视频教程全集

    python接口自动化测试视频教程全集 下载地址:https://k.weidian.com/Pfm=DyuI 课程内容: 第一章:接口测试基础 1-1 接口自动化课程简介 1-2 接口测试课程大纲 ...

  7. python接口自动化测试面试题_Python 接口自动化测试实战

    Python接口自动化测试实战 简介 本课程主要围绕Python相关库再服务端接口自动化测试中的应用展开介绍,重点讲解接口自动化基础.编写接口自动化脚本.框架原理.项目实战,此外还扩展介绍多用例管理与 ...

  8. 11小时 python自动化测试从入门到_从设计到开发Python接口自动化测试框架实战,资源教程下载...

    课程名称 从设计到开发Python接口自动化测试框架实战,资源教程下载 课程简介: 课程从接口基础知识入门,从抓包开始,到接口工具的运用,再到常见接口库.接口开发.Mock服务.unittest框架的 ...

  9. python接口自动化测试框架实战从设计到开发_Python接口自动化测试框架实战 从设计到开发...

    第1章 课程介绍(不要错过) 本章主要讲解课程的详细安排.课程学习要求.课程面向用户等,让大家很直观的对课程有整体认知! 第2章 接口测试工具Fiddler的运用 本章重点讲解如何抓app\web的h ...

最新文章

  1. Golang的time包:秒、毫秒、纳秒时间戳输出
  2. 浓缩版java8新特性
  3. ASP.NET MVC5+EF6+EasyUI 后台管理系统(92)-打印EasyUI 的datagrid表格
  4. 【HDU - 1540】 Tunnel Warfare (线段树进阶操作 区间合并+ 单点更新+ 最长覆盖区间查询 )
  5. 设计模式行为模式_使用行为模式建立很棒的社区
  6. centos 关机命令_Linux anacron命令用法详解
  7. (194)FPGA上电后IO的默认状态(ISE软件默认为0)
  8. 该设备或资源(Web代理)未设置为接受端口“7890“上的连接解决方案
  9. java jackson_Jackson 框架的高阶应用
  10. android nfc MifareUltralight读写
  11. 周志华与「深度森林」
  12. 小米发布会的米8探索者——很吓人的技术分析
  13. 五猴分桃python_猴子分桃问题 | 学步园
  14. 计算机系统原理实验——微程序控制器
  15. MPB:中农冯固组-​利用13C-DNA-SIP法示踪根际和菌丝际活性解磷细菌
  16. Qt获取鼠标位置(绝对位置、相对位置)
  17. CISCO学习笔记(四)Trunk功能及端口模式
  18. 用TensorFlow和TFSlim实现图像分类与分割
  19. Firefox必备组件备忘
  20. Flask开发实现在线问答系统的问题发布功能

热门文章

  1. python mkl 锐龙 cpu_锐龙4800h笔记本安装Ubuntu20.04.1填坑实录
  2. Flutter 主题(皮肤)更换的那些事
  3. 信创环境下微服务无法注册到注册中心:Notify connected event to listeners
  4. 未能启动环境模拟器服务器,exagear启动环境失败 游戏电脑问题解决分享!
  5. 读书笔记|指数型函数对算法的影响实际应用-day3
  6. 【数据安全 每周资讯】“杀猪盘”蔓延至数字资产,有人被骗百万,有人依然爱“男友”
  7. 软件开发培训学校三大服务体系
  8. 计算机公式固定数值符号,中级无纸化机考 数学公式/符号你会输入吗?
  9. 为CSDN添加打赏收钱功能
  10. 华为平板M5青春版让你从此开启智能语音之旅