1. 前言

今年2月调去支持项目接口测试,测试过程中使用过postman、jmeter工具,基本能满足使用,但是部分情况下使用较为麻烦。
比如:部分字段存在唯一性校验或字段间有业务性校验,每次请求均需手工修改部分报文内容,使用工具难以满足实际使用。
因此,萌生了使用python去实现接口自动化的想法。之前未接触过接口测试,但有一点编程基础,经过2个多月的磕磕碰碰,不断完善,经历2次重构之后,最后基本达成了目标。

2. 技术栈

  • python语言
  • requests库
  • unittest单元测试框架
  • HTMLTestReportCN、BeautifulReport测试报告

3. 实现的功能概述

  • 支持post、get等请求类型,xml、json格式的报文
  • 支持使用excel编写测试用例,测试用例支持涉及多接口的场景用例;支持按脚本的形式编写测试用例
  • 支持测试结果保存至数据库,支持生成html报告,支持将生成的测试结果导出到excel文件
  • 支持邮件发送测试结果
  • 支持多线程并发执行测试用例

4. 框架及项目结构

APIS_AutoTest

api: 主程序目录

comm:公共函数,包括:接口请求基类、请求及相应数据操作基类等

intf_handle:接口操作层,包含:接口初始化、断言等

business:业务实现部分

utils:工具类,包括:读取文件、发送邮件、excel操作、数据库操作、日期时间格式化等

config:配置文件目录,包含yaml配置文件、以及路径配置

data:测试数据目录,用于存放测试数据

temp:临时文件目录,用于存放临时文件

result:结果目录

report:测试报告目录,用于存放生成的html报告

details:测试结果详情目录,用于存放生成的测试用例执行结果excel文件

log:日志文件目录

test:测试用例、测试集相关目录,启动test_suite执行用例文件存放在此

test_case:测试用例存放路径

test_suite:测试模块集,按模块组装用例

5. 测试用例执行流程

  • 以脚本形式编写的测试用例执行流程图:

     
  • 以excel形式编写的测试用例执行流程图:

     

6. 核心方法设计

  • 接口请求基类

    RequestBase(request_type, url, header, body, data_type=None)

功能描述:

根据传入的请求类型,请求地址,请求头,请求体,发送接口请求,获得响应头,响应体

  • request_type: 请求类型,只能是'GET', 'POST', 'HEAD', 'OPTIONS', 'PUT', 'DELETE', 'TRACE', 'CONNECT'中的一个,无大小写要求
  • url: 请求地址,完整的接口地址
  • head: 请求头
  • body:请求体,xml格式请求体为字符串;json格式请求体需传入json格式
  • data_type: 数据类型,未使用字段,备用。可为:xml、json

支持方法

__send_request()
发送请求,类私有方法,初始化时调用,调用方法返回:响应对象

get_respond()
获取响应对象,调用方法返回:响应对象

get_respond_head()
获取响应头,调用方法返回:响应头

get_respond_body()
获取响应体,调用方法返回:响应体


  • 请求或响应数据操作基类

    RequestRespondHandle(data_type, body, fields, value_dict=None)

功能描述:

根据传入的xml、json格式请求体或响应体,读取字段的值或更新字段的值

参数描述:

  • data_type: 数据类型,当前仅支持xml、json,不区分大小写
  • body: 请求体或响应体,json格式请求体支持传入字典、json格式数据(自动转换为字典)
  • fields: 字段名称,支持多字段传入,支持数据类型:字符串、元组、列表。多字段传入形式,字符串:多个字段名称之间逗号隔开,如:'a1,b2';元组、列表正常传入即可。json格式:需写入完整节点路径,如:body.base.name,对应list类型的需传入索引位置,如:body.baselist[0].name
  • value_dict: 字段值字典,以字段名称及字段值键值对的方式存储数据,读取字段值时,一般不需传入,也支持传入(用于读取excel形式流程前后传值);更新字段值时,需传入,且字典中的key值需要与fields中的字段名对应。

支持方法:

get_fields_value()
获取字段值,调用方法即可获取到xml或json格式的请求体或响应体对应的字段值,并返回:字段值字典

update_fields_value()
更新字段值,调用方法即可更新请求体中对应的字段值,返回更新后的请求体。
Json格式请求体返回格式为字典。

不足与改进:

xml格式,读取或更新字段时,若存在多个相同名称字段,默认只选第一个;
json格式,读取嵌套列表的时候,未支持按列表读取,当前需精确位置单个读取或更新;


  • 请求体初始化-接口映射类

    RequestMsgInitMapper(data_type, intf_code, request_body, **kwargs)

功能描述:

根据传入的接口编号,映射到对应的接口请求报文的初始化方法,进行接口初始化

参数描述:

  • data_type: 数据类型,执行xml、json,不区分大小写
  • intf_code: 接口编号,每个接口都要一个接口编号,根据接口编号可以唯一确定一个接口
  • request_body: 请求体,json格式请求体支持传入字典、json格式数据
  • **kwargs: 可变关键字参数,每个接口初始化时,参数数量均不一致,所有需要使用可变参数,需要以a=value这种方式传入。部分字段(如id)可在方法内设置生成方法,一般可变参数设置都是用于前后接口字段传值。

支持方法:

start_Intf_init_mapper()
启动 接口初始化映射,该方法通过判断传入数据类型、接口编号,执行对应的接口初始化方法。
调用方法后,进行对应接口的初始化,并返回初始化后的请求体。
Json格式的数据,返回json格式的请求体(不管传入的是字典格式、还是json格式)。

适用场景说明:

接口映射类主要是针对不同接口初始化字段涉及复杂业务判断,需按接口单独编写的场景。
如果一个接口初始化涉及的字段均不涉及业务相关的复杂判断处理,可以直接统一使用通用接口初始化方法进行初始化。


通用接口初始化方法

intf_base_init(data_type, request_body, **kwargs)

功能描述:

根据可变关键字参数传入的键值对,进行接口报文初始化,返回初始化后的请求体

参数描述:

  • data_type: 数据类型,执行xml、json,不区分大小写
  • request_body: 请求体,json格式请求体支持传入字典、json格式数据
  • **kwargs:可变关键字参数,每个接口初始化时,参数数量均不一致,所有需要进行初始化的字段,均需以键值对的方式传入。

适用场景说明:

接口初始化字段不涉及复杂逻辑判断,直接传值后更新即可;该方式也适用于接口请求头的初始化。

7. 数据操作类

数据操作主要是数据读取、写入,主要分为如下几类:

- 读取txt、json等文件内容(整个读取)、写入内容到文件
- 读取yaml文件内容
- 读取excel表格内容、导出excel表格、读取excel并作为模板
- 操作数据库表中的数据(增删改查)

读取txt/json文件内容,写入内容到文件

FileHandle(file_name, file_path)

功能描述:

读取文件所有内容

参数描述:

  • file_name:读取/写入文件名称,包含后缀,支持txt,json等
  • file_path:读取/写入文件所在的目录

支持方法:

read_file_content()
读取文件内容,并以字符串返回

write_to_file(content)
将内容写入文件


读取yaml文件内容

ReadYaml(file_path)

功能描述:

读取yaml文件内,支持按名称读取

参数描述:

  • file_path:读取文件的完整路径

支持方法:

get_yaml()
读取yaml文件所有内容,并以字典格式返回

get_value(level_name)
读取yaml文件字段的值,并返回
level_name: 节点字段名称,如涉及多节点需传入对应路径,如:db.host


读取excel表格内容、导出excel表格、读取excel并作为模板

ExcelHandle()

功能描述:

读取excel表格内容、将数据导出到excle表格中

支持方法:

read_excel_data(excel_path, sheet_name=None)

读取excel表格内容,并以列表嵌套列表的方式返回

excel_path: 读取excel文件的完整路径
sheet_name: 读取excel的页签名称,默认读取第一个页签

export_to_excel(data, head, file_name, time_flag=None)

将数据导出到excle表格中

data: 需要导出到excel的内容,元组嵌套元组(对应数据库中查询返回的结果)
head: excel表头,列表、元组嵌套元组(数据库中查询表头、描述,支持多表头)
file_name: excel文件名称,包含后缀.xlsx,excel导出路径系统默认
time_flag: 时间戳标记,可传入用例执行的报告号,使其对应

copy_excel_template(template_path, sheet_name=None)

复制excel表格模板,返回workbook, workssheet

template_path: 模板文件完整路径
sheet_name: 模板文件页签名称,默认第一个页签


操作数据库数据

功能描述:

  • 表数据读取:根据情况读取所需数据,返回数据格式元组嵌套元组。
  • 插入/更新表数据:根据实际内容,写入数据到对应的表中

备注:

数据库部分封装为数据库操作类

8. 部分关键代码

接口请求基类

# create by: wyun
# create at: 2020/4/19 9:30
import json import requests class RequestBase: def __init__(self, request_type, request_url, request_body, request_headers, intf_type=None):
"""
请求接口公共类
:param request_type: 请求类型:post, get
:param request_url: 请求url地址
:param request_headers: 请求头
:param request_body: 请求体(xml类型传入字符串格式,json类型数据必须传入json格式,不能传入字典)
:param intf_type: 接口类型:webservice, api,分别对应接口数据格式:xml, json
"""
self.request_type = request_type
self.request_url = request_url
self.request_body = request_body
self.request_headers = request_headers self.intf_type = intf_type
self.res = self.__send_request() # 发送请求
def __send_request(self):
if not isinstance(self.request_type, str):
print('请求类型格式错误。')
return None
if self.request_type.upper() not in ['GET', 'POST', 'HEAD', 'OPTIONS', 'PUT', 'DELETE', 'TRACE', 'CONNECT']:
print('请求类型不存在。')
return None
return requests.request(method=self.request_type, url=self.request_url, data=self.request_body.encode('utf-8'),
headers=self.request_headers) # 获取响应
def get_respond(self):
return self.res # 获取响应头
def get_respond_head(self):
return self.res.headers # 获取响应体
def get_respond_body(self):
if self.res.content:
return self.res.text

请求或响应数据操作基类

# create by: wyun
# create at: 2020/4/19 12:58
import json
import re """
对请求体或响应体进行处理:
1. 支持读取请求体或响应体中字段的值
2. 支持更新请求体中字段的值
""" # 递归调用,更新json中的字段值
def update_json_step(node, json_str, value, i=0):
# 当前节点索引(负向)
node_index = -len(node) + i # 如果包含[n]形式,说明节点为列表,需处理
if '[' in node[node_index] and ']' in node[node_index]:
list_node, list_index = node[node_index].split('[')
index = list_index.split(']')[0]
if index is None or index == '':
print('参数传入错误,请指定列表[%s]索引' % list_node)
else:
index = int(index)
return update_json_step(node, json_str[list_node][index], value, i + 1) # 判断如果当前节点为最后一个节点,则更新value值
if node_index == -1:
json_str[node[-1]] = value
return json_str return update_json_step(node, json_str[node[node_index]], value, i + 1) class DataHandle: def __init__(self, data_type, data_msg, fields, value_dict=None):
"""
处理请求体或响应体数据,读取或更新字段值
:param data_type: 数据类型:xml,json
:param data_msg: 请求体或响应体
:param fields: 字段值,支持多字读方式,字段间逗号隔开;或传入列表、元组
json格式:需写入完整节点路径,如:body.base.name,对应list类型的需传入索引位置,如:body.baselist[0].name
:param value_dict: 以字典键值对保存字段值
"""
self.data_type = data_type
self.data = data_msg
# fields支持str,list方式,str自动转换为list
if isinstance(fields, str):
self.fields = fields
self.fields_list = []
if ',' in fields:
self.fields_list = fields.strip().split(',')
else:
self.fields_list.append(fields.strip())
elif isinstance(fields, list):
self.fields_list = fields
else:
self.fields_list = list(fields)
# 初始化字典值
if value_dict is None:
self.value_dict = dict()
else:
self.value_dict = value_dict def get_fields_value(self):
"""
获取字段值
1. 支持获取多个字段值,输入字符串或列表, 元组
"""
step_dict = self.value_dict
# 字段列表循环获取
for p in self.fields_list:
if p == '':
continue
# 处理xml格式报文
if self.data_type.upper() == 'XML':
pattern = '<' + p + '>.*</' + p + '>'
search_result = re.search(pattern, self.data)
if search_result is not None:
# 包括标签和值都匹配上
field_and_value = search_result.group()
# 去除标签获取字段值,并存入字典
step_dict[p] = (field_and_value.split('</')[0]).split('>')[-1]
else:
# print('参数[%s]提取失败,无匹配值。' % p)
pass
# 处理json格式报文
elif self.data_type.upper() == 'JSON':
# 获取层级(使用.隔开)
node = p.split('.')
# json转换字典
if isinstance(self.data, dict):
temp = self.data
else:
temp = json.loads(self.data) # 逐层读取数据
for per_node in node:
if '[' in per_node and ']' in per_node:
per_node, index_str = per_node.split('[')
index = int(index_str.split(']')[0])
temp = temp.get(per_node)[index]
if temp is None:
break
else:
temp = temp.get(per_node)
if temp is None:
break
# 讲读取的结果写入字典
step_dict[p] = temp
# 非 xml,json的数据格式,报错退出
else:
print('ERROR: 不支持此数据类型[%s]' % self.data_type)
break
return step_dict def update_fields_value(self):
""" 更新字段值 支持更新单个字段值,多个字段值(字段名 字符串或列表、元组,字段值 字典) """ req_body = self.data
# 字段列表循环更新
for p in self.fields_list:
# 入参类型判断
if isinstance(self.value_dict, dict):
# 参数在字典中不存在,则跳过
if p not in self.value_dict.keys():
print('ERROR: 字典中不存在参数[%s]。' % p)
continue
# 获取字典中参数的值
value = self.value_dict[p]
elif isinstance(self.value_dict, str):
value = self.value_dict
else:
print('函数入参[%s]类型不支持,请传入字符串或字典。' % self.value_dict)
break
# 更新数据类型为 xml 的参数值
if self.data_type.upper() == 'XML':
# 优先寻找是否存在指定更新参数,若有,则按指定参数更新
if '${' + p + '}' in req_body:
req_body = req_body.replace('${' + p + '}', value)
# 其次寻找标签参数值,若有,则按标签更新值
else:
# 检查标签是否存在,若存在
if '<' + p + '>' in req_body and '</' + p + '>' in req_body:
# 正则匹配
pattern = '<' + p + '>.*</' + p + '>'
new_field_and_value = '<' + p + '>' + str(value) + '</' + p + '>'
search_result = re.search(pattern, req_body)
# 若正则匹配结果存在,则进行字段值更新
if search_result is not None:
old_field_and_value = search_result.group()
req_body = req_body.replace(old_field_and_value, new_field_and_value)
else:
print('参数[%s]匹配标签失败,请检查报文中标签格式。' % p)
else:
# print('参数[%s]匹配标签失败,无此标签。' % p)
continue
# 更新数据类型为 xml 的参数值
elif self.data_type.upper() == 'JSON':
# json转换字典
if isinstance(self.data, dict):
req_body = self.data
else:
req_body = json.loads(self.data)
# 获取层级
node = p.split('.')
# 调用递归函数更新字段值
update_json_step(node, req_body, value) # 非 xml,json的数据格式,报错退出
else:
print('ERROR: 不支持此数据类型[%s]' % self.data_type)
break return req_body

接口初始化通用类

# create by: wyun
# create at: 2020/4/23 22:42
from api.comm.data_handle import DataHandle def intf_base_init(data_type, request_body, **kwargs):
"""
接口初始化通用类
:param data_type: 数据类型,如:xml,json
:param request_body: 请求体
:param kwargs: 可变关键字参数
:return: 请求体
"""
return DataHandle(data_type, request_body, kwargs.keys(), kwargs).update_fields_value() 如果你处于想学Python自动化或者正在学习Python自动化,Python自动化的教程不少了吧,但是是最新的吗?
说不定你学了可能是一年前人家就学过的内容,干货分享一波,2020最新的Python教程。

python+requests实现接口自动化的更多相关文章

  1. python requests简单接口自动化

    get方法 url:显而易见,就是接口的地址url啦 headers:定制请求头(headers),例如:content-type = application/x-www-form-urlencode ...

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

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

  3. 纯python自研接口自动化脚本更新版本,让小白也能实现0到1万+的接口自动化用例

    查看完整文章点击原文链接:纯python自研接口自动化脚本更新版本,让小白也能实现0到1万+的接口自动化用例 你是否还在用postman\jmeter做接口自动化吗?用python的开源框架[unit ...

  4. python脚本实现接口自动化轻松搞定上千条接口用例

    接口自动化目前是测试圈主流的一个话题,我也在网上搜索了很多关于自动化的关键词,大多数博主分享的python做接口自动化都是以开源的框架,比如:pytest.unittest+ddt(数据驱动) 最常见 ...

  5. 基于Python+requests搭建的自动化框架-实现流程化的接口串联

    框架产生目的:公司走的是敏捷开发模式,编写这种框架是为了能够满足当前这种发展模式,用于前后端联调之前(后端开发完接口,前端还没有将业务处理完毕的时候)以及日后回归阶段,方便为自己腾出学(mo)习(yu ...

  6. python入门以及接口自动化实践

    一.Python入门必备基础语法# 标识符:python中我们自己命名的都是标识符# 项目名 包名 模块名# 变量名 函数名 类名# 1:字母 下划线 数字组成 命名的时候不能以数字开头# 2:见名知 ...

  7. 新手入门贴之基于 python 语言的接口自动化 demo 小实战

    大家好,我是正在学习接口测试的菜鸟.近期通过自己的学习,完成了一个关于测试接口的接口自动化demo.下面想跟大家分享一下,主要的思路是根据接口文档确定测试用例,并将测试用例写在excel中.因为只是小 ...

  8. python+requests+unittest执行自动化接口测试

    1.安装requests.xlrd.json.unittest库 <1>pip 命令安装: pip install requestspip install xlrdpip install ...

  9. python+unittest+requests实现接口自动化

    前言: Requests简介 Requests 是使用 Apache2 Licensed 许可证的 HTTP 库.用 Python 编写,真正的为人类着想. Python 标准库中的 urllib2  ...

随机推荐

  1. 一篇看懂Docker

    松勤教育2020.4.20 我要分享     Docker 是什么? Docker 属于 Linux 容器的一种封装,提供简单易用的容器使用接口.它是目前最流行的 Linux 容器解决方案. Dock ...

  2. position两种绝对定位的区别

    position绝对定有两种,分别为absolute和fixed 一.共同点: 1.改变行内元素的呈现方式,display被置为inline:block 2.让元素脱离普通流,不占据空间 3.默认会覆 ...

  3. vscode 常用变量

    ${workspaceFolder} the path of the workspace folder that contains the tasks.json file ${workspaceRoo ...

  4. ca13a_c++_顺序容器的操作6删除元素

    /*ca13a_c++_顺序容器的操作6删除元素c.erase(p) //删除迭代器p指向的位置c.erase(b,e) //删除b to e之间的数据,迭代器b包括,e不包括c.clear()//删 ...

  5. 04.开发REST 接口

    使用Django开发REST 接口 我们以在Django框架中使用的图书英雄案例来写一套支持图书数据增删改查的REST API接口,来理解REST API的开发. 在此案例中,前后端均发送JSON格式 ...

  6. 基于flask框架的高校舆情分析系统

    系统分析: 高校舆情分析拟实现如下功能,采集微博.贴吧.学校官网的舆情信息,对这些舆情进行数据分析.情感分析,提取关键词,生成词云分析,情感分析图,实时监测舆情动态. 系统设计: 前端:采用layui ...

  7. Linux中bash的一些命令

    Linux——bash的简单使用 bash及其特性: 1.bash实质上是一个可执行的程序,一个用户的工作环境. 2.每一个shell下可以再打开一个shell,新打开的shell称为子shell,每 ...

  8. 【DP-动态代理】JDK&Cglib

    需求:增强未知方法的代码 简单方案:继承或者聚合 继承,调用方法前后加增强逻辑 聚合 - 静态代理 持有被代理类对象 或者接口 可通过嵌套实现代理的组合 和 装饰器模式很像 高级方案 代理所有的类,不 ...

  9. 重学 Java 设计模式:实战迭代器模式「模拟公司组织架构树结构关系,深度迭代遍历人员信息输出场景」

    作者:小傅哥 博客:https://bugstack.cn - 原创系列专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 相信相信的力量! 从懵懂的少年,到拿起键盘,可以写一个Hell ...

  10. Spring IoC 默认标签解析

    前言 本系列全部基于 Spring 5.2.2.BUILD-SNAPSHOT 版本.因为 Spring 整个体系太过于庞大,所以只会进行关键部分的源码解析. 本篇文章主要介绍 Spring IoC 容 ...