本文总结分享介绍接口测试框架开发,环境使用python3+selenium3+unittest+ddt+requests测试框架及ddt数据驱动,采用Excel管理测试用例等集成测试数据功能,以及使用HTMLTestRunner来生成测试报告,目前有开源的poman、Jmeter等接口测试工具,为什么还要开发接口测试框架呢?因接口测试工具也有存在几点不足。

  • 测试数据不可控制。比如接口返回数据不可控,就无法自动断言接口返回的数据,不能断定是接口程序引起,还是测试数据变化引起的错误,所以需要做一些初始化测试数据。接口工具没有具备初始化测试数据功能,无法做到真正的接口测试自动化。
  • 无法测试加密接口。实际项目中,多数接口不是可以随便调用,一般情况无法摸拟和生成加密算法。如时间戳和MDB加密算法,一般接口工具无法摸拟。
  • 扩展能力不足。开源的接口测试工具无法实现扩展功能。比如,我们想生成不同格式的测试报告,想将测试报告发送到指定邮箱,又想让接口测试集成到CI中,做持续集成定时任务。

测试框架处理流程

测试框架处理过程如下:

  1. 首先初始化清空数据库表的数据,向数据库插入测试数据;
  2. 调用被测试系统提供的接口,先数据驱动读取excel用例一行数据;
  3. 发送请求数据,根据传参数据,向数据库查询得到对应的数据;
  4. 将查询的结果组装成JSON格式的数据,同时根据返回的数据值与Excel的值对比判断,并写入结果至指定Excel测试用例表格;
  5. 通过单元测试框架断言接口返回的数据,并生成测试报告,最后把生成最新的测试报告HTML文件发送指定的邮箱。

测试框架结构目录介绍

目录结构介绍如下:

  • config/:                    文件路径配置
  • database/:               测试用例模板文件及数据库和发送邮箱配置文件
  • db_fixture/:              初始化接口测试数据
  • lib/:                          程序核心模块。包含有excel解析读写、发送邮箱、发送请求、生成最新测试报告文件
  • package/:                存放第三方库包。如HTMLTestRunner,用于生成HTML格式测试报告
  • report/:                    生成接口自动化测试报告
  • testcase/:                用于编写接口自动化测试用例
  • run_demo.py:          执行所有接口测试用例的主程序
  • GitHub项目地址:    https://github.com/yingoja/DemoAPI

数据库封装

 [tester]
name = Jason [mysqlconf]
host = 127.0.0.1
port = 3306
user = root
password = 123456
db_name = guest [user]
# 发送邮箱服务器
HOST_SERVER = smtp.163.com
# 邮件发件人
FROM = 111@163.com
# 邮件收件人
TO = 222@126.com
# 发送邮箱用户名/密码
user = aaa
password = aaa
# 邮件主题
SUBJECT = 发布会系统接口自动化测试报告

config.ini

 #!/usr/bin/env python
# _*_ coding:utf-8 _*_
__author__ = 'YinJia' import os,sys
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from config import setting
from pymysql import connect,cursors
from pymysql.err import OperationalError
import configparser as cparser # --------- 读取config.ini配置文件 ---------------
cf = cparser.ConfigParser()
cf.read(setting.TEST_CONFIG,encoding='UTF-8')
host = cf.get("mysqlconf","host")
port = cf.get("mysqlconf","port")
user = cf.get("mysqlconf","user")
password = cf.get("mysqlconf","password")
db = cf.get("mysqlconf","db_name") class DB:
"""
MySQL基本操作
"""
def __init__(self):
try:
# 连接数据库
self.conn = connect(host = host,
user = user,
password = password,
db = db,
charset = 'utf8mb4',
cursorclass = cursors.DictCursor
)
except OperationalError as e:
print("Mysql Error %d: %s" % (e.args[0],e.args[1])) # 清除表数据
def clear(self,table_name):
real_sql = "delete from " + table_name + ";"
with self.conn.cursor() as cursor:
# 取消表的外键约束
cursor.execute("SET FOREIGN_KEY_CHECKS=0;")
cursor.execute(real_sql)
self.conn.commit() # 插入表数据
def insert(self, table_name, table_data):
for key in table_data:
table_data[key] = "'"+str(table_data[key])+"'"
key = ','.join(table_data.keys())
value = ','.join(table_data.values())
real_sql = "INSERT INTO " + table_name + " (" + key + ") VALUES (" + value + ")" with self.conn.cursor() as cursor:
cursor.execute(real_sql)
self.conn.commit() # 关闭数据库
def close(self):
self.conn.close() # 初始化数据
def init_data(self, datas):
for table, data in datas.items():
self.clear(table)
for d in data:
self.insert(table, d)
self.close()

mysql_db.py

 #!/usr/bin/env python
# _*_ coding:utf-8 _*_
__author__ = 'YinJia' import sys, time, os
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from db_fixture.mysql_db import DB # 定义过去时间
past_time = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(time.time()-100000))
# 定义将来时间
future_time = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(time.time()+10000)) # 创建测试数据
datas = {
# 发布会表数据
'sign_event':[
{'id':1,'name':'红米Pro发布会','`limit`':2000,'status':1,'address':'北京会展中心','start_time':future_time},
{'id':2,'name':'苹果iphon6发布会','`limit`':1000,'status':1,'address':'宝安体育馆','start_time':future_time},
{'id':3,'name':'华为荣耀8发布会','`limit`':2000,'status':0,'address':'深圳福田会展中心','start_time':future_time},
{'id':4,'name':'苹果iphon8发布会','`limit`':2000,'status':1,'address':'深圳湾体育中心','start_time':past_time},
{'id':5,'name':'小米5发布会','`limit`':2000,'status':1,'address':'北京国家会议中心','start_time':future_time},
],
# 嘉宾表数据
'sign_guest':[
{'id':1,'realname':'Tom','phone':13511886601,'email':'alen@mail.com','sign':0,'event_id':1},
{'id':2,'realname':'Jason','phone':13511886602,'email':'sign@mail.com','sign':1,'event_id':1},
{'id':3,'realname':'Jams','phone':13511886603,'email':'tom@mail.com','sign':0,'event_id':5},
],
} # 测试数据插入表
def init_data():
DB().init_data(datas)

test_data.py

 #!/usr/bin/env python
# _*_ coding:utf-8 _*_
__author__ = 'YinJia' import os,sys
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
sys.path.append(BASE_DIR) # 配置文件
TEST_CONFIG = os.path.join(BASE_DIR,"database","config.ini")
# 测试用例模板文件
SOURCE_FILE = os.path.join(BASE_DIR,"database","DemoAPITestCase.xlsx")
# excel测试用例结果文件
TARGET_FILE = os.path.join(BASE_DIR,"report","excelReport","DemoAPITestCase.xlsx")
# 测试用例报告
TEST_REPORT = os.path.join(BASE_DIR,"report")
# 测试用例程序文件
TEST_CASE = os.path.join(BASE_DIR,"testcase")

setting.py

程序核心模块

 #!/usr/bin/env python
# _*_ coding:utf-8 _*_
__author__ = 'YinJia' import os def new_report(testreport):
"""
生成最新的测试报告文件
:param testreport:
:return:返回文件
"""
lists = os.listdir(testreport)
lists.sort(key=lambda fn: os.path.getmtime(testreport + "\\" + fn))
file_new = os.path.join(testreport,lists[-1])
return file_new

netReport.py

 #!/usr/bin/env python
# _*_ coding:utf-8 _*_
__author__ = 'YinJia' import xlrd class ReadExcel():
"""读取excel文件数据"""
def __init__(self,fileName, SheetName="Sheet1"):
self.data = xlrd.open_workbook(fileName)
self.table = self.data.sheet_by_name(SheetName) # 获取总行数、总列数
self.nrows = self.table.nrows
self.ncols = self.table.ncols
def read_data(self):
if self.nrows > 1:
# 获取第一行的内容,列表格式
keys = self.table.row_values(0)
listApiData = []
# 获取每一行的内容,列表格式
for col in range(1, self.nrows):
values = self.table.row_values(col)
# keys,values组合转换为字典
api_dict = dict(zip(keys, values))
listApiData.append(api_dict)
return listApiData
else:
print("表格是空数据!")
return None

readexcel.py

 #!/usr/bin/env python
# _*_ coding:utf-8 _*_
__author__ = 'YinJia' import os,sys,json
sys.path.append(os.path.dirname(os.path.dirname(__file__))) class SendRequests():
"""发送请求数据"""
def sendRequests(self,s,apiData):
try:
#从读取的表格中获取响应的参数作为传递
method = apiData["method"]
url = apiData["url"]
if apiData["params"] == "":
par = None
else:
par = eval(apiData["params"])
if apiData["headers"] == "":
h = None
else:
h = eval(apiData["headers"])
if apiData["body"] == "":
body_data = None
else:
body_data = eval(apiData["body"])
type = apiData["type"]
v = False
if type == "data":
body = body_data
elif type == "json":
body = json.dumps(body_data)
else:
body = body_data #发送请求
re = s.request(method=method,url=url,headers=h,params=par,data=body,verify=v)
return re
except Exception as e:
print(e)

sendrequests.py

 #!/usr/bin/env python
# _*_ coding:utf-8 _*_
__author__ = 'YinJia' import os,sys
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from config import setting
import smtplib
from lib.newReport import new_report
import configparser
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart def send_mail(file_new):
"""
定义发送邮件
:param file_new:
:return: 成功:打印发送邮箱成功;失败:返回失败信息
"""
f = open(file_new,'rb')
mail_body = f.read()
f.close()
#发送附件
con = configparser.ConfigParser()
con.read(setting.TEST_CONFIG,encoding='utf-8')
report = new_report(setting.TEST_REPORT)
sendfile = open(report,'rb').read()
# --------- 读取config.ini配置文件 ---------------
HOST = con.get("user","HOST_SERVER")
SENDER = con.get("user","FROM")
RECEIVER = con.get("user","TO")
USER = con.get("user","user")
PWD = con.get("user","password")
SUBJECT = con.get("user","SUBJECT") att = MIMEText(sendfile,'base64','utf-8')
att["Content-Type"] = 'application/octet-stream'
att.add_header("Content-Disposition", "attachment", filename=("gbk", "", report)) msg = MIMEMultipart('related')
msg.attach(att)
msgtext = MIMEText(mail_body,'html','utf-8')
msg.attach(msgtext)
msg['Subject'] = SUBJECT
msg['from'] = SENDER
msg['to'] = RECEIVER try:
server = smtplib.SMTP()
server.connect(HOST)
server.starttls()
server.login(USER,PWD)
server.sendmail(SENDER,RECEIVER,msg.as_string())
server.quit()
print("邮件发送成功!")
except Exception as e:
print("失败: " + str(e))

sendmail.py

 #!/usr/bin/env python
# _*_ coding:utf-8 _*_
__author__ = 'YinJia' import os,sys
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
import shutil
from config import setting
from openpyxl import load_workbook
from openpyxl.styles import Font,Alignment
from openpyxl.styles.colors import RED,GREEN,DARKYELLOW
import configparser as cparser # --------- 读取config.ini配置文件 ---------------
cf = cparser.ConfigParser()
cf.read(setting.TEST_CONFIG,encoding='UTF-8')
name = cf.get("tester","name") class WriteExcel():
"""文件写入数据"""
def __init__(self,fileName):
self.filename = fileName
if not os.path.exists(self.filename):
# 文件不存在,则拷贝模板文件至指定报告目录下
shutil.copyfile(setting.SOURCE_FILE,setting.TARGET_FILE)
self.wb = load_workbook(self.filename)
self.ws = self.wb.active def write_data(self,row_n,value):
"""
写入测试结果
:param row_n:数据所在行数
:param value: 测试结果值
:return: 无
"""
font_GREEN = Font(name='宋体', color=GREEN, bold=True)
font_RED = Font(name='宋体', color=RED, bold=True)
font1 = Font(name='宋体', color=DARKYELLOW, bold=True)
align = Alignment(horizontal='center', vertical='center')
# 获数所在行数
L_n = "L" + str(row_n)
M_n = "M" + str(row_n)
if value == "PASS":
self.ws.cell(row_n, 12, value)
self.ws[L_n].font = font_GREEN
if value == "FAIL":
self.ws.cell(row_n, 12, value)
self.ws[L_n].font = font_RED
self.ws.cell(row_n, 13, name)
self.ws[L_n].alignment = align
self.ws[M_n].font = font1
self.ws[M_n].alignment = align
self.wb.save(self.filename)

writeexcel.py

接口测试用例编写

 #!/usr/bin/env python
# _*_ coding:utf-8 _*_
__author__ = 'YinJia' import os,sys
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
import unittest,requests,ddt
from config import setting
from lib.readexcel import ReadExcel
from lib.sendrequests import SendRequests
from lib.writeexcel import WriteExcel testData = ReadExcel(setting.SOURCE_FILE, "Sheet1").read_data() @ddt.ddt
class Demo_API(unittest.TestCase):
"""发布会系统"""
def setUp(self):
self.s = requests.session() def tearDown(self):
pass @ddt.data(*testData)
def test_api(self,data):
# 获取ID字段数值,截取结尾数字并去掉开头0
rowNum = int(data['ID'].split("_")[2])
# 发送请求
re = SendRequests().sendRequests(self.s,data)
# 获取服务端返回的值
self.result = re.json()
# 获取excel表格数据的状态码和消息
readData_code = int(data["status_code"])
readData_msg = data["msg"]
if readData_code == self.result['status'] and readData_msg == self.result['message']:
OK_data = "PASS"
WriteExcel(setting.TARGET_FILE).write_data(rowNum + 1,OK_data)
if readData_code != self.result['status'] or readData_msg != self.result['message']:
NOT_data = "FAIL"
WriteExcel(setting.TARGET_FILE).write_data(rowNum + 1,NOT_data)
self.assertEqual(self.result['status'], readData_code, "返回实际结果是->:%s" % self.result['status'])
self.assertEqual(self.result['message'], readData_msg, "返回实际结果是->:%s" % self.result['message']) if __name__=='__main__':
unittest.main()

testAPI.py

集成测试报告

 #!/usr/bin/env python
# _*_ coding:utf-8 _*_
__author__ = 'YinJia' import os,sys
sys.path.append(os.path.dirname(__file__))
from config import setting
import unittest,time
from HTMLTestRunner import HTMLTestRunner
from lib.sendmail import send_mail
from lib.newReport import new_report
from db_fixture import test_data
from package.HTMLTestRunner import HTMLTestRunner def add_case(test_path=setting.TEST_CASE):
"""加载所有的测试用例"""
discover = unittest.defaultTestLoader.discover(test_path, pattern='*API.py')
return discover def run_case(all_case,result_path=setting.TEST_REPORT):
"""执行所有的测试用例""" # 初始化接口测试数据
test_data.init_data() now = time.strftime("%Y-%m-%d %H_%M_%S")
filename = result_path + '/' + now + 'result.html'
fp = open(filename,'wb')
runner = HTMLTestRunner(stream=fp,title='发布会系统接口自动化测试报告',
description='环境:windows 7 浏览器:chrome',
tester='Jason')
runner.run(all_case)
fp.close()
report = new_report(setting.TEST_REPORT) #调用模块生成最新的报告
send_mail(report) #调用发送邮件模块 if __name__ =="__main__":
cases = add_case()
run_case(cases)

run_demo.py

测试结果展示

  • HTML测试结果报告:

  • Excel测试用例结果

  • 邮件收到的测试报告

python_接口自动化测试框架的更多相关文章

  1. 接口测试入门(4)--接口自动化测试框架 / list和map用法 / 随机选取新闻 (随机数生成) / 接口相关id映射

    一.接口自动化测试框架 为了更好的组织测试方法,测试用例并且持续集成,我们选择了  java+testNG(测试用例组织)+gitlab(代码版本管理)+Jenkins(持续集成工具) 作为一整套的自 ...

  2. python版接口自动化测试框架源码完整版(requests + unittest)

    python版接口自动化测试框架:https://gitee.com/UncleYong/my_rf [框架目录结构介绍] bin: 可执行文件,程序入口 conf: 配置文件 core: 核心文件 ...

  3. 接口自动化 [授客]基于python+Testlink+Jenkins实现的接口自动化测试框架V3.0

    基于python+Testlink+Jenkins实现的接口自动化测试框架V3.0   by:授客 QQ:1033553122     博客:http://blog.sina.com.cn/ishou ...

  4. 接口自动化 基于python+Testlink+Jenkins实现的接口自动化测试框架[V2.0改进版]

    基于python+Testlink+Jenkins实现的接口自动化测试框架[V2.0改进版]   by:授客 QQ:1033553122 由于篇幅问题,,暂且采用网盘分享的形式: 下载地址: [授客] ...

  5. 基于python+Testlink+Jenkins实现的接口自动化测试框架V3.0

    基于python+Testlink+Jenkins实现的接口自动化测试框架V3.0 目录 1. 开发环境2. 主要功能逻辑介绍3. 框架功能简介 4. 数据库的创建 5. 框架模块详细介绍6. Tes ...

  6. 【转】robot framework + python实现http接口自动化测试框架

    前言 下周即将展开一个http接口测试的需求,刚刚完成的java类接口测试工作中,由于之前犯懒,没有提前搭建好自动化回归测试框架,以至于后期rd每修改一个bug,经常导致之前没有问题的case又产生了 ...

  7. 【python3+request】python3+requests接口自动化测试框架实例详解教程

    转自:https://my.oschina.net/u/3041656/blog/820023 [python3+request]python3+requests接口自动化测试框架实例详解教程 前段时 ...

  8. 接口自动化 基于python实现的http+json协议接口自动化测试框架源码(实用改进版)

    基于python实现的http+json协议接口自动化测试框架(实用改进版)   by:授客 QQ:1033553122 欢迎加入软件性能测试交流QQ群:7156436     目录 1.      ...

  9. Python 基于python实现的http接口自动化测试框架(含源码)

    基于python实现的http+json协议接口自动化测试框架(含源码) by:授客 QQ:1033553122      欢迎加入软件性能测试交流 QQ群:7156436  由于篇幅问题,采用百度网 ...

随机推荐

  1. 解题:POI 2007 Driving Exam

    题面 有点意思的题 从一个位置$i$出发可以到达每一个位置即是从$1,n$出发可以到达$i$.然后有了一个做法:把图上下反转后建反图,这样就可以求从一个点$i$到达左右两侧的花费$dp[i][0/1] ...

  2. rovio视觉里程计的笔记

    rovio是一个紧耦合,基于图像块的滤波实现的VIO. 他的优点是:计算量小(EKF,稀疏的图像块),但是对应不同的设备需要调参数,参数对精度很重要.没有闭环,没有mapping thread.经常存 ...

  3. struts的namespace理解

    转载: namespace决定了action的访问路径,默认为"",可以接受所有路径的action namespace可以写为/,或者/xxx,或者/xxx/yyy,对应的acti ...

  4. Chapter4(表达式) --C++Prime笔记

    1.重载运算符:为已经存在的运算符赋予另外一层含义. 2.左值与右值:   ①当一个对象被用作右值的时候,用的是对象的值(内容):当一个对象被用作左值的时候,用的是对象的身份(在内存中的位置). ②在 ...

  5. 字符串化#、拼接字符##和可变参数宏(...和_ _VA_ARGS_ _)

    宏定义的使用与注意事项 ##是一个连接符号,用于把参数连在一起 #是“字符串化”的意思.出现在宏定义中的#是把跟在后面的参数转换成一个字符串#define paster( n ) printf( &q ...

  6. qsort和sort

    sort()函数是C++中的排序函数其头文件为:#include<algorithm>头文件: qsort()是C中的排序函数,其头文件为:#include<stdlib.h> ...

  7. php与Git下基于webhook的自动化部署

    前言 2018年第一篇文章,没啥技术含量,权当笔记 我们一般都会用git或者svn来管理我们的代码 每次代码更新后还要手动的去把服务器上的代码也更新一遍 项目小了还好 项目大了着实浪费时间 要是服务器 ...

  8. 5 Kafka 应用问题经验积累

    16.Kafka 配置文件同步 为了给kafka的进程添加GC日志信息,方便在以后重启的时候,加入GC日志: 修改bin/kafka-server-start.sh: export KAFKA_OPT ...

  9. Spyder简述

    导言 想打造轮子, 就必须要有一套完善的造轮子的工具. 我在jupyter+sciTE的组合里转来转去, 最后还是打算放弃这个组合, 因为离开了自动完成/调用提示/随时随地的访问文档帮助, 前行之路太 ...

  10. 多进程+协程 处理IO问题

    from multiprocessing import Pool import gevent,os import time def recursion(n): if n == 1 or n ==2: ...