最强实战,接口自动化测试Python3+Requests+Unittest+ddt框架封装(详细)

news2024/11/18 13:43:38

目录:导读

    • 前言
    • 一、Python编程入门到精通
    • 二、接口自动化项目实战
    • 三、Web自动化项目实战
    • 四、App自动化项目实战
    • 五、一线大厂简历
    • 六、测试开发DevOps体系
    • 七、常用自动化测试工具
    • 八、JMeter性能测试
    • 九、总结(尾部小惊喜)


前言

接口自动化测试框架

环境使用python3+unittest+ddt+requests测试框架及ddt数据驱动,采用Excel管理测试用例等集成测试数据功能,以及使用HTMLTestRunner来生成测试报告。

目前有开源的poman、Jmeter等接口测试工具,为什么还要开发接口测试框架呢?

接口测试工具也有存在几点不足:
测试数据不可控制:比如接口返回数据不可控,就无法自动断言接口返回的数据,不能断定是接口程序引起,还是测试数据变化引起的错误,所以需要做一些初始化测试数据。

接口工具没有具备初始化测试数据功能,无法做到真正的接口测试自动化。

无法测试加密接口:实际项目中,多数接口不是可以随便调用,一般情况无法摸拟和生成加密算法。如时间戳和MDB加密算法,一般接口工具无法摸拟。

扩展能力不足:开源的接口测试工具无法实现扩展功能。比如,我们想生成不同格式的测试报告,想将测试报告发送到指定邮箱,又想让接口测试集成到CI中,做持续集成定时任务。

测试框架处理流程

21

测试框架处理过程如下:
首先初始化清空数据库表的数据,向数据库插入测试数据;
调用被测试系统提供的接口,先数据驱动读取excel用例一行数据;
发送请求数据,根据传参数据,向数据库查询得到对应的数据;

将查询的结果组装成JSON格式的数据,同时根据返回的数据值与Excel的值对比判断,并写入结果至指定Excel测试用例表格;
通过单元测试框架断言接口返回的数据,并生成测试报告,最后把生成最新的测试报告HTML文件发送指定的邮箱。

22

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

数据库的封装

config.ini文件

[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 = 发布会系统接口自动化测试报告

mysql_db.py文件

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文件

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)

setting.py文件

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")

程序核心代码

netReport.py文件

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

readexcel.py文件

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

sendrequests.py文件

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)

sendmail.py文件

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))

writeexcel.py文件

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)

接口测试用例编写

testAPI.py

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()

集成测试报告

run_demo.py文件

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)
下面是我整理的2023年最全的软件测试工程师学习知识架构体系图

一、Python编程入门到精通

请添加图片描述

二、接口自动化项目实战

请添加图片描述

三、Web自动化项目实战

请添加图片描述

四、App自动化项目实战

请添加图片描述

五、一线大厂简历

请添加图片描述

六、测试开发DevOps体系

请添加图片描述

七、常用自动化测试工具

请添加图片描述

八、JMeter性能测试

请添加图片描述

九、总结(尾部小惊喜)

奋斗之路,一步一个脚印,努力耕耘方能开启成功的大门。困难只是暂时的阻碍,坚持才能攀登高峰。相信自己的潜力,追求卓越的目标,勇敢迎接挑战,绽放出不可思议的光芒!

每一次坚持,都是对自己的成长最好的投资;每一次努力,都是为了让未来的自己更加出色。勇敢面对困难,奋力拼搏,只有不停奋斗,才能创造属于自己的辉煌人生!

脚步坚定,心怀信念,奋斗是征服梦想的航船。无畏挑战,超越极限,只有拼搏才能绽放人生的光芒。勇攀巅峰,书写传奇,用汗水浇筑属于自己的壮丽篇章!勇往直前,奋斗不息,追逐胜利的脚步永不停歇!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/837009.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RDP远程桌面服务的RD授权过期解决方法

RDP远程桌面服务的RD授权过期解决方法 一、打开远程桌面会话主机配置二、打开远程桌面授权模式属性三、选择远程桌面授权模式四、添加许可证服务器五、选择确定保存设置六、RD授权设置成功七、查看“授权诊断” 一、打开远程桌面会话主机配置 二、打开远程桌面授权模式属性 双…

35岁,不是体能衰老的分界线!

35岁,不是体能衰老的分界线 1. 35岁以上求职者同比增长14.9%,体能下滑成为35岁以上年龄群体的标签。 2. 35岁人群开始感觉经常失眠、腰背痛和肥胖,体力不济可能是常年累积的不规律作息和饮食习惯导致。 3. 35岁以后体能下滑,无法高…

视频添加字幕

1、依靠ffmpeg 命令 package zimu;import java.io.IOException;public class TestSrt {public static void main(String[] args) {String videoFile "/test/test1.mp4";String subtitleFile "/test/test1.SRT";String outputFile "/test/testout13…

openpnp - 吸嘴站(Nozzle Tip Changer)的选择

文章目录 openpnp - 吸嘴站(Nozzle Tip Changer)的选择概述磁铁吸嘴库带抱轴的吸嘴库我的吸嘴库选择我的吸嘴库实现 - 磁铁吸嘴库吸嘴座主体吸嘴座上盖我的吸嘴库实现 - 带抱轴的吸嘴库吸嘴座主体拔吸嘴时的受力挡板抱轴层上盖备注END openpnp - 吸嘴站(Nozzle Tip Changer)的选…

同城预约上门小程序开发:为用户带来便捷与个性化的服务体验“

上门服务小程序开发具有许多优势,下面我将介绍一些重要的优点。   方便快捷:上门服务小程序可以让用户随时随地通过手机进行预约和安排上门服务。无需等待电话回复或亲自前往实体店面,用户可以直接在小程序中选择时间、服务类型和地点&…

openGauss学习笔记-29 openGauss 高级数据管理-UNION子句

文章目录 openGauss学习笔记-29 openGauss 高级数据管理-UNION子句29.1 语法格式29.2 示例29.2.1 UNION29.2.2 UNION ALL openGauss学习笔记-29 openGauss 高级数据管理-UNION子句 UNION计算多个SELECT语句返回行集合的并集。UNION内部的SELECT语句必须拥有相同数量的列&#…

《剑指offer》(4)二叉树篇

二叉树深度有两种递归思路: (1)递归返回当前的深度,当root是空时,返回0 (2)将当前深度和节点一起传入递归,设置全局变量,每经过一个节点就更新全局变量的值。 方法一&a…

控制台窗口和powershell运行服务会卡住的解决办法

问题描述 在 windows 环境下开发的时候,使用 PowerShell 执行 python.exe test.py 等命令经常会出现程序会卡在不动的问题。这时候需要到控制台按一下回车程序才会继续往下执行。 解决办法 原因: 控制台开启了快速编辑模式的情况下,如果鼠标选中了文本…

弘扬“两弹一星”精神,勇攀科学技术高峰——道本科技商业大学党日活动圆满落幕

2023年8月2日,道本科技与商业大学携手举办了一场主题为“弘扬‘两弹一星’精神,勇攀科学技术高峰”的党日活动。本次活动旨在了解党领导下的中国核工业发展历程,传承和弘扬“两弹一星”精神,同时展示道本科技创新产品,…

【力扣】链表题目总结

文章目录 链表基础题型一、单链表翻转、反转、旋转1.反转链表2.反转链表II——反转部分链表3.旋转链表4.K个一组翻转链表5.反转偶数长度组的节点 二、删除单链表中的结点1.删除链表的结点2.删除未排序链表中的重复节点3.删除已排序链表中的重复元素I——重复元素只剩下一个4.删…

带你简单认识淘宝 API 接口,API接口适用场景和业务类型

淘宝 API 接口是为开发电商类应用程序而设计的一套完整的、跨浏览器、跨平台的接口规范。通过开放接口,开发者可以不改变现有系统,直接在原有系统上实现新功能。该规范于 2007 年发布,是目前业界唯一完整覆盖电商系统各相关业务领域的接口标准…

C语言假期作业 DAY 13

一、选择题 1、如果 x2014 ,下面函数的返回值是( ) int fun(unsigned int x) { int n 0; while(x 1) { n; x x | (x 1); } return n; } A: 20 B: 21 C: 23 D 25 答案解析 正确答案:C 这个作用是对整型中0的个数进行统计&…

安卓手机录屏怎么把小白点去掉?试试这种方法

随着安卓手机功能的不断升级,录屏已经成为了一项基本功能。然而,当我们录制完视频后,常常会发现视频中有许多小白点,影响了视频的美观度。那么,如何去除这些小白点呢?本文将为大家介绍几种简单易行的方法。…

Linux操作系统块设备参数调优

目录 一、队列深度 二、调度算法 三、预读量 四、I/O对齐 一、队列深度 队列深度决定了给块设备写I/O的最大并发数,对于Linux系统,默认值为128,一般情况下不建议用户修改此参数。用户可以使用cat命令查询当前块设备队列深度。 linux-ob3a…

Arthas GC日志-JVM(十八)

上篇文章说jvm的实际运行情况。 Jvm实际运行情况-JVM(十七) Arthas介绍 因为arthas完全是java代码写的,我们直接用命令启动: Java -jar arthas-boot.jar 启动成功后,选择我们项目的进程。 进入我们可用dashboard…

ATFX汇评:非农就业报告来袭,汇市或迎剧烈波动

ATFX汇评:美国非农就业报告每月发布一次,其中非农就业人口和失业率两项数据最受关注。7月季调后非农就业人口,将于今日20:30公布,前值为20.9万人,预期值20万人;7月失业率,同一时间公布&#xff…

文字转语音

键盘获取文字,转化为语音后保存本地 from win32com.client import Dispatch from comtypes.client import CreateObject from comtypes.gen import SpeechLib speakerDispatch(SAPI.SpVoice) speaker.Speak(请输入你想转化的文字) datainput(请输入:)#s…

【二等奖方案】Web攻击检测与分类识别赛题「机器学习」团队解题思路

2022 CCF BDCI 数字安全公开赛 赛题「Web攻击检测与分类识别」 地址:http://go.datafountain.cn/4Zj 机器学习战队 获奖方案 团队简介 我们团队由五名成员组成,对机器学习都非常感兴趣,同时在机器学习领域有着丰富的实战经验&#xff0c…

Teams Room视频会议室方案

需求背景: 适合在40平米的会议室参加Teams视频会议,会议桌周围可以坐20人,要求: 1,操作简单,一键入会Teams Room; 2,任何人带上自己的笔记本电脑,可以分享电脑画面&#…

uniapp 持续获取定位(登录状态下才获取)(不采用定时器)(任意页面都可监听定位改变)

基于上次文章做了优化和改良,保证在登录状态下才获取定位信息 uniapp 小程序实时且持续获取定位信息(全局设置一次)(单页面监听定位改变)(不采用定时器)_uniapp小程序定位_前端小胡兔的博客-CSDN博客本篇文章实现了uniapp 微信小程序实时获取定位信息,小程序打开即可持续获取定…