废话

最近在自己学习接口自动化测试,这里也算是完成一个小的成果,欢迎大家交流指出不合适的地方,源码在文末

问题

整体代码结构优化未实现,导致最终测试时间变长,其他工具单接口测试只需要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中用例数据
xlutils 2.0.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:
# 服务器host地址,发送请求的url= host+ path
test: http://127.0.0.1:8888/api/private/v1/
dev: http://47.115.124.102:8888/api/private/v1/ response_reg:
# 提取token的表达式
token: $.data.token
# 提取实际响应中的某部分来作为断言数据(实例中断言的是meta这个子字典,预期结果也是写的meta子字典中的内容)
response: $.meta 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:
user: 发件人邮箱
password: 邮箱授权码(不是密码)
host: smtp.163.com
contents: 解压apiAutoReport.zip(接口测试报告)后,请使用已安装Live Server 插件的VsCode,打开解压目录下的index.html查看报告
# 发件人列表
addressees: ["123@qq.com","12067@qq.com","717@qq.com"]
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 loguru import logger
import requests class BaseRequest(object):
def __init__(self):
pass # 请求
def base_requests(self, method, url, data=None, file_var=None, file_path=None, header=None):
""" :param method: 请求方法
:param url: 接口path
:param data: 数据,请传入dict样式的字符串
:param file_path: 上传的文件路径
:param file_var: 接口中接收文件对象的参数名
:param header: 请求头
:return: 完整的响应对象
"""
session = requests.Session()
if (file_var in [None, '']) and (file_path in [None, '']):
files = None
else:
# 文件不为空的操作
files = {file_var: open(file_path, 'rb')}
# get 请求参数传递形式 params
if method == 'get':
res = session.request(method=method, url=url, params=data, headers=header)
else:
res = session.request(method=method, url=url, data=data, files=files, headers=header)
logger.info(f'请求方法:{method},请求路径:{url}, 请求参数:{data}, 请求文件:{files}, 请求头:{header})')
return res.json()
#!/usr/bin/env/python3
# -*- coding:utf-8 -*-
"""
@project: apiAutoTest
@author: zy7y
@file: read_data.py
@ide: PyCharm
@time: 2020/7/31
"""
import json import jsonpath
import xlrd
from xlutils.copy import copy
from loguru import logger class ReadData(object):
def __init__(self, excel_path):
self.excel_file = excel_path
self.book = xlrd.open_workbook(self.excel_file) def get_data(self):
""" :return:
"""
data_list = []
title_list = [] table = self.book.sheet_by_index(0)
for norw in range(1, table.nrows):
# 每行第4列 是否运行
if table.cell_value(norw, 3) == '否':
continue
# 每行第3列, 标题单独拿出来
title_list.append(table.cell_value(norw, 1)) # 返回该行的所有单元格组成的数据 table.row_values(0) 0代表第1列
case_number = table.cell_value(norw, 0)
path = table.cell_value(norw, 2)
is_token = table.cell_value(norw, 4)
method = table.cell_value(norw, 5)
file_var = table.cell_value(norw, 6)
file_path = table.cell_value(norw, 7)
dependent = table.cell_value(norw, 8)
data = table.cell_value(norw, 9)
expect = table.cell_value(norw, 10)
actual = table.cell_value(norw, 11)
value = [case_number, path, is_token, method, file_var, file_path, dependent, data, expect, actual]
logger.info(value)
# 配合将每一行转换成元组存储,迎合 pytest的参数化操作,如不需要可以注释掉 value = tuple(value)
value = tuple(value)
data_list.append(value)
return data_list, title_list def write_result(self, case_number, result):
""" :param case_number: 用例编号:case_001
:param result: 需要写入的响应值
:return:
"""
row = int(case_number.split('_')[1])
logger.info('开始回写实际响应结果到用例数据中.')
result = json.dumps(result, ensure_ascii=False)
new_excel = copy(self.book)
ws = new_excel.get_sheet(0)
# 11 是 实际响应结果栏在excel中的列数-1
ws.write(row, 11, result)
new_excel.save(self.excel_file)
logger.info(f'写入完毕:-写入文件: {self.excel_file}, 行号: {row + 1}, 列号: 11, 写入值: {result}') # 读实际的响应
def read_actual(self, depend):
""" :param nrow: 列号
:param depend: 依赖数据字典格式,前面用例编号,后面需要提取对应字段的jsonpath表达式
{"case_001":["$.data.id",],}
:return:
"""
depend = json.loads(depend)
# 用来存依赖数据的字典
depend_dict = {}
for k, v in depend.items():
# 得到行号
norw = int(k.split('_')[1])
table = self.book.sheet_by_index(0)
# 得到对应行的响应, # 11 是 实际响应结果栏在excel中的列数-1
actual = json.loads(table.cell_value(norw, 11))
try:
for i in v:
logger.info(f'i {i}, v {v}, actual {actual} \n {type(actual)}')
depend_dict[i.split('.')[-1]] = jsonpath.jsonpath(actual, i)[0]
except TypeError as e:
logger.error(f'实际响应结果中无法正常使用该表达式提取到任何内容,发现异常{e}')
return depend_dict
#!/usr/bin/env/python3
# -*- coding:utf-8 -*-
"""
@project: apiAutoTest
@author: zy7y
@file: test_api.py
@ide: PyCharm
@time: 2020/7/31
"""
import json
import shutil import jsonpath
from loguru import logger
import pytest
import allure
from api.base_requests import BaseRequest
from tools.read_config import ReadConfig
from tools.read_data import ReadData 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() data_list, title_ids = ReadData(case_data_path).get_data() br = BaseRequest()
token_header = {}
no_token_header = {} class TestApiAuto(object): def start_run_test(self):
import os
if os.path.exists('../report') and os.path.exists('../log'):
shutil.rmtree(path='../report')
shutil.rmtree(path='../log')
logger.add(log_path) pytest.main(args=[f'--alluredir={report_data}'])
# # 启动一个web服务的报告
# os.system('allure serve ./report/data')
os.system(f'allure generate {report_data} -o {report_generate} --clean')
logger.debug('报告已生成') def treating_data(self, is_token, dependent, data):
if is_token == '':
header = no_token_header
else:
header = token_header
logger.info(f'处理依赖时data的数据:{data}')
if dependent != '':
dependent_data = ReadData(case_data_path).read_actual(dependent)
logger.debug(f'依赖数据解析获得的字典{dependent_data}')
if data != '':
# 合并组成一个新的data
dependent_data.update(json.loads(data))
data = dependent_data
logger.debug(f'data有数据,依赖有数据时 {data}')
else:
# 赋值给data
data = dependent_data
logger.debug(f'data无数据,依赖有数据时 {data}')
else:
if data == '':
data = None
logger.debug(f'data无数据,依赖无数据时 {data}')
else:
data = json.loads(data)
logger.debug(f'data有数据,依赖无数据 {data}')
return data, header @pytest.mark.parametrize('case_number,path,is_token,method,file_var,'
'file_path,dependent,data,expect,actual', data_list, ids=title_ids)
def test_main(self, case_number, path, is_token, method, file_var, file_path,
dependent, data, expect, actual): with allure.step("处理相关数据依赖,header"):
data, header = self.treating_data(is_token, dependent, data)
with allure.step("发送请求,取得响应结果的json串"):
res = br.base_requests(method=method, url=base_url + path, file_var=file_var, file_path=file_path,
data=data, header=header)
with allure.step("将响应结果的内容写入用例中的实际结果栏"):
ReadData(case_data_path).write_result(case_number, res) # 写token的接口必须是要正确无误能返回token的
if is_token == '写':
with allure.step("从登录后的响应中提取token到header中"):
token_header['Authorization'] = jsonpath.jsonpath(res, token_reg)[0]
logger.info(f'token_header: {token_header}, \n no_token_header: {no_token_header}')
with allure.step("根据配置文件的提取响应规则提取实际数据"):
really = jsonpath.jsonpath(res, res_reg)[0]
with allure.step("处理读取出来的预期结果响应"):
expect = eval(expect)
with allure.step("预期结果与实际响应进行断言操作"):
assert really == expect
logger.info(f'完整的json响应: {res}\n 需要校验的数据字典: {really}\n 预期校验的数据字典: {expect}\n 测试结果: {really == expect}') if __name__ == '__main__':
from tools.zip_file import zipDir
from tools.send_email import send_email
t1 = TestApiAuto()
t1.start_run_test()
zipDir(report_generate, report_zip)
send_email(email_setting)

运行结果

致谢

jsonpath语法学习:https://blog.csdn.net/liuchunming033/article/details/106272542

zip文件压缩:https://www.cnblogs.com/yhleng/p/9407946.html

这算是学习接口自动化的第一个成果,但是要应用生产环境,拿过去还需要改很多东西,欢迎交流。

源码地址

源码地址: https://gitee.com/zy7y/apiAutoTest.git

Python接口自动化测试框架: pytest+allure+jsonpath+requests+excel实现的接口自动化测试框架(学习成果)的更多相关文章

  1. Pytest单元测试框架——Pytest+Allure+Jenkins的应用

    一.简介 pytest+allure+jenkins进行接口测试.生成测试报告.结合jenkins进行集成. pytest是python的一种单元测试框架,与python自带的unittest测试框架 ...

  2. python+requests+excel+unittest+ddt接口自动化数据驱动并生成html报告(已弃用)

    前言 1.环境准备: python3.6 requests xlrd openpyxl HTMLTestRunner_api 2.目前实现的功能: 封装requests请求方法 在excel填写接口请 ...

  3. python+requests+excel+unittest+ddt接口自动化数据驱动并生成html报告

    1.环境准备: python3.6 requests xlrd openpyxl HTMLTestRunner_api 2.目前实现的功能: 封装requests请求方法 在excel填写接口请求参数 ...

  4. python+requests+excel+unittest+ddt接口自动化数据驱动并生成html报告(二)

    可以参考 python+requests接口自动化完整项目设计源码(一)https://www.cnblogs.com/111testing/p/9612671.html 原文地址https://ww ...

  5. Python3+Requests+Excel完整接口自动化框架

    框架整体使用Python3+Requests+Excel:包含对实时token的获取 框架结构图 1.------base -------runmethond.py runmethond:对不同的请求 ...

  6. Python+Pytest+Allure+Git+Jenkins接口自动化框架

    Python+Pytest+Allure+Git+Jenkins接口自动化框架 一.接口基础 接口测试是对系统和组件之间的接口进行测试,主要是效验数据的交换,传递和控制管理过程,以及相互逻辑依赖关系. ...

  7. 接口自动化框架(Pytest+request+Allure)

    前言: 接口自动化是指模拟程序接口层面的自动化,由于接口不易变更,维护成本更小,所以深受各大公司的喜爱. 接口自动化包含2个部分,功能性的接口自动化测试和并发接口自动化测试. 本次文章着重介绍第一种, ...

  8. 【转】自动化测试框架: pytest&allure ,提高自动化健壮性和稳定性

    序 在之前,我写过一个系列“从零开始搭建一个简单的ui自动化测试框架(pytest+selenium+allure)”,在这个系列里,主要介绍了如何从零开始去搭建一个可用的自动化工程框架,但是还缺乏了 ...

  9. python+requests+excel 接口自动化框架

    一.项目框架如图: 1.common :这个包都是一些公共的方法,如:手机号加解密,get/post接口请求的方法封装,接口鉴权,发邮件,读写excel文件方法等等 2.result:存放每次运行的l ...

随机推荐

  1. 好看的UI框架

    一.Web 1.semantic-ui: https://semantic-ui.com/elements/divider.html 二.H5 1.BUI: http://www.easybui.co ...

  2. python实现二维码、条形码识别

    环境: python 3.7 Win7 依赖包安装: pip install pillow pip install opencv-python pip install opencv-contrib-p ...

  3. CTF_show平台 web题解 part3

    web13 题目显示文件上传,各类型上传均提示错误,在使用ctf-scan扫描的时候,发现upload.php.bak. 查看源码: <?php header("content-typ ...

  4. java 面向对象(三十八):反射(二) Class类的理解与获取Class的实例

    1.Class类的理解 1.类的加载过程:程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾).接着我们使用java.exe命令对某个字节码文件进行解释运行.相当于将某个 ...

  5. scrapy 基础组件专题(九):scrapy-redis 源码分析

    下面我们来看看,scrapy-redis的每一个源代码文件都实现了什么功能,最后如何实现分布式的爬虫系统: connection.py 连接得配置文件 defaults.py 默认得配置文件 dupe ...

  6. Python网络编程02 /基于TCP、UDP协议的socket简单的通信、字符串转bytes类型

    Python网络编程02 /基于TCP.UDP协议的socket简单的通信.字符串转bytes类型 目录 Python网络编程02 /基于TCP.UDP协议的socket简单的通信.字符串转bytes ...

  7. wiremock技术入门

    mock用于制作测试桩,是非常好用的自动化测试mock工具  一.下载 进入官网的下载地址: http://wiremock.org/docs/running-standalone/

  8. 访问控制列表与SSH结合使用,为网络设备保驾护航,提高安全性

    通过之前的文章简单介绍了华为交换机如何配置SSH远程登录,在一些工作场景,需要特定的IP地址段能够SSH远程访问和管理网络设备,这样又需要怎么配置呢?下面通过一个简单的案例带着大家去了解一下. 要实现 ...

  9. 适用于IE8浏览器的bootsarp下拉菜单(支持多选,全选)

    html部分代码,引用及整体项目Github项目地址:https://github.com/CNbozi/combobox 1 <!DOCTYPE html> <html lang= ...

  10. k_means算法+python实现

    文章目录 一.原理 二.算法步骤 三.实例如下: 四.python代码实现: 一.原理 K均值算法使用的聚类准则函数是误差平方和准则,通过反复迭代优化聚类结果,使所有样本到各自所属类别的中心的距离平方 ...