python + requsts 接口测试框架的分层设计与ddt(数据驱动)

python + requsts 接口测试框架的分层设计与ddt(数据驱动)

分成设计如下图的三层结构(也可以设计成两层,注:分层设计仅仅是一个很简单的设计模式)

  • common包存放的是路径处理模块、配置文件处理模块,数据处理模块等等。以下以该框架举例。
    • connectdb.py模块是对数据库连接的封装其内容如下:
# -*- coding: utf-8 -*-
"""
    ***********************
    @author:   - FrankLee(李学春)
    @time:     - 2020/11/4
    @qq:       - 775729278
    @wechat:   - lxc18286562925
    @FileName: - connectdb.py
    ***********************
"""

"""
    连接数据库
"""
import pymysql, pymongo, redis, pymssql
from common.handleconfig import conf
class DbUtils(object):
    def __init__(self, sql):
        self.sql = sql
        """
        判断数据库类型
        """
        if self.sql == "mysql":
            '''
            连接mysql数据库
            '''
            self.conn = pymysql.connect(host=conf.get("db", "host"),
                                        ussr=conf.get("db", "user"),
                                        password=conf.get("db", "password"),
                                        database=conf.get("db", "database"),
                                        port=conf.get("db", "port")
                                        )
            '''
            获取游标对象
            '''
            self.cur = self.conn.cursor()
            
        elif self.sql == "mongodb":
            self.conn = pymongo.MongoClient()

        elif self.sql == "redis":
            self.conn = redis.Redis()

        elif self.sql == "orcal":
            pass
        else:
            print("数据库类型错误")

    def find_one(self, sql):
        """
        查询一条数据
        :param sql: sal语句
        :return:
        """
        self.cur.execute(sql)
        return self.cur.fetchone()

    def find_all(self, sql):
        """
        查询所有数据
        :param sql: sql语句
        :return:
        """
        self.cur.execute(sql)
        return self.cur.fetchall()

    def close_all(self):
        """
        关闭游标和连接
        :return: None
        """
        self.cur.close()
        self.conn.close()
# 以上只是一个半成品,只是对mysql的一个封装,其中的mongodb,redis、orcal、sqlserver等未写完
# 封装方法大同小异
    • handleconfig.py 模块是对配置文件的处理,其内容如下(结合conf下的config.ini来理解):
# -*- coding: utf-8 -*-
"""
    ***********************
    @author:   - FrankLee(李学春)
    @time:     - 2020/11/4
    @qq:       - 775729278
    @wechat:   - lxc18286562925
    @FileName: - handleconfig.py
    ***********************
"""

"""
    处理config的配置信息
    导入configparser下的ConfigPaser 处理ini文件
"""
from configparser import ConfigParser
from common.handlepath import *

class HandleConfig(ConfigParser):
    def __init__(self, filename):
        """
        先继承父类的构造函数
        """
        super().__init__()
        self.filename = filename
        """
        读取文件
        """
        self.read(filename)

    def write_data(self, section, option, value=None):
        """
        向配置文件ini上写如信息
        """
        self.set(section, option, value)
        """
        将内容写到文件里去
        """
        self.write(fp=open(self.filename))

conf = HandleConfig(os.path.join(CONFDIR, "config.ini"))
    • handleemail.py模块是用来封装邮件发送的,其内容如下:
# -*- coding: utf-8 -*-
"""
    ***********************
    @author:   - FrankLee(李学春)
    @time:     - 2020/11/4
    @qq:       - 775729278
    @wechat:   - lxc18286562925
    @FileName: - handleconfig.py
    ***********************
"""

import os
import smtplib
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from common.handleconfig import conf


def send_email(filename, title):
    """
    发送邮件的功能函数
    :param filename: 文件的路径
    :param title:   邮件的主题
    :return:
    """
    """
    第一步:连接邮箱的smtp服务器,并登录
    """
    smtp = smtplib.SMTP_SSL(host=conf.get("email", "host"), 
                            port=conf.getint("email", "port"))
    smtp.login(user=conf.get("email", "user"), 
               password=conf.get("email", "pwd"))
	"""
    第二步:构建一封邮件
    创建一封多组件的邮件
    """
    msg = MIMEMultipart()

    with open(filename, "rb") as f:
        content = f.read()
    """
    创建邮件文本内容
    """
    text_msg = MIMEText(content, _subtype="html", _charset="utf8")
    """
    添加到多组件的邮件中
    """
    msg.attach(text_msg)
    """
    创建邮件的附件
    """
    report_file = MIMEApplication(content)
    report_file.add_header('content-disposition', 'attachment',
                           filename=os.path.split(filename)[-1])
    """
    将附件添加到多组件的邮件中
    """
    msg.attach(report_file)
	"""
    主题
    """
    msg["Subject"] = title
    """
    发件人
    """
    msg["From"] = conf.get("email", "from_addr")
    """
    收件人
    """
    msg["To"] = conf.get("email", "to_addr")
	"""
    第三步:发送邮箱
    """
    smtp.send_message(msg, from_addr=conf.get("email", "from_addr"), 
                      to_addrs=conf.get("email", "to_addr"))
    • handlelog.py 模块封装的是对日志的处理,内容如下:
# -*- coding: utf-8 -*-
"""
    ***********************
    @author:   - FrankLee(李学春)
    @time:     - 2020/11/5
    @qq:       - 775729278
    @wechat:   - lxc18286562925
    @FileName: - handlelog.py
    ***********************
"""

import logging
import os
from common.handleconfig import conf
from common.handlepath import LOGDIR


class HandleLog(object):
    @staticmethod
    def create_loger():
        """
        创建日志收集,设置日志等级
        :return:
        """
        mylog = logging.getLogger(conf.get("log", "name"))
        mylog.setLevel(conf.get("log", "level"))

        """
        创建输出到控制台的日志等级,
        并添加到日志收集器
        """
        sh = logging.StreamHandler()
        sh.setLevel(conf.get("log", "sh_level"))
        mylog.addHandler(sh)

        """
        创建输出到文件的日志等级
        """
        fh = logging.FileHandler(os.path.join(LOGDIR, "log.log"))
        fh.setLevel(conf.get("log","fh_level"))
        mylog.addHandler(fh)

        """
        定义输出日志的格式
        """
        formater = "%(asctime)s - [%(filename)s-->line:%(lineno)d] - %(levelname)s:%(message)s"
        fm = logging.Formatter(formater)
        sh.setFormatter(fm)
        fh.setFormatter(fm)
        return mylog

log = HandleLog.create_loger()
    • handlepath.py 是对整个项目路径的封装,有利于移植和后续的操作,内容如下:
# -*- coding: utf-8 -*-
"""
    ***********************
    @author:   - FrankLee(李学春)
    @time:     - 2020/11/4
    @qq:       - 775729278
    @wechat:   - lxc18286562925
    @FileName: - handlepath.py
    ***********************
"""
import os
# 定义项目的路径
BASEDIR = os.path.dirname(os.path.dirname(__file__))

# 定义conf配置文件的路径
CONFDIR = os.path.join(BASEDIR, "conf").replace("\\", "/")

# 定义data的路径
DATADIR = os.path.join(BASEDIR, "data").replace("\\", "/")

# 定义log的路径
LOGDIR = os.path.join(BASEDIR, "log").replace("\\", "/")

# 定义report的路径
REPORTDIR = os.path.join(BASEDIR, "report").replace("\\", "/")

# 定义testcase的路径
TESTDIR = os.path.join(BASEDIR, "testcase").replace("\\", "/")

if __name__ == "__main__":
    print(BASEDIR)
    print(CONFDIR)
    print(DATADIR)
    print(LOGDIR)
    print(REPORTDIR)
    print(TESTDIR)
    • handlerequests.py 模块是对requests库的再次封装,以便减轻后续代码量,内容如下:
# -*- coding: utf-8 -*-
"""
    ***********************
    @author:   - FrankLee(李学春)
    @time:     - 2020/11/4
    @qq:       - 775729278
    @wechat:   - lxc18286562925
    @FileName: - handlerequests.py
    ***********************
"""

"""
    处理requsts请求
"""
import requests
class SendRequest(object):
    def __init__(self):
        """
        让登录和登录后的接口保持在同一个会话当中,上下文管理
        """
        self.session = requests.Session()

    def send(self, method, url, params=None, data=None, headers=None, json=None, cookies=None, files=None):
        """
        :param method:  请求方法
        :param url:     请求地址
        :param params:  get请求的参数
        :param data:    post请求application/x-www-form-urlencoded格式的参数
        :param headers: 请求头部信息
        :param json:    post请求json格式
        :param cookies:
        :param files:   请求上穿文件
        :return:
        """

        """
        将请求方法转变为小写
        """
        method = method.lower()
        """
        判断请求方法
        """
        try:
            if method == "get":
                resp = self.session.get(url, params=params, headers=headers)

            elif method == "post":
                resp = self.session.post(url=url, data=data,headers=headers)
        except Exception as e:
            print(e)
        else:
            if method == "get":
                resp = self.session.get(url, params=params, headers=headers, verify=False)

            elif method == "post":
                resp = self.session.post(url=url, data=data,headers=headers, verify=False)

        return resp
    • readexcel.py 模块是要读取excel中的用例和测试数据,其内容如下:
# -*- coding: utf-8 -*-
"""
    ***********************
    @author:   - FrankLee(李学春)
    @time:     - 2020/11/4
    @qq:       - 775729278
    @wechat:   - lxc18286562925
    @FileName: - readexcel.py
    ***********************
"""

"""
    读取excel的数据
"""
import openpyxl
import os
from common.handlepath import *

class ReadExcel(object):
    def __init__(self, filename, sheetname):
        """
        :param filename: excel的名称
        :param sheetname: 工作簿的名称
        """
        self.filename = filename
        self.sheetname = sheetname

    def __open(self):
        """
        打开excel表格
        :return:
        """
        self.wb = openpyxl.load_workbook(self.filename)
        self.sh = self.wb[self.sheetname]

    def readData(self):
        """
        打开工作簿,获取数据
        :return:
        """
        self.__open()
        """
        取每一行的数据当到元组中
        """
        datas = list(self.sh.rows)
        """
        获取表头的数据
        """
        tile = [i.value for i in datas[0]]
        """
        定义一个列表来接收所有的用例
        """
        cases = []
        for i in datas[1:]:
            """
            把title变成键,把后边的变成值
            """
            case = dict(zip(tile, [j.value for j in i]))
            cases.append(case)
        return cases

    def writeData(self, row, column, value):
        """
        数据回写
        :return:
        """
        self.__open()
        self.sh.cell(row, column, value)
        self.wb.save(self.filename)
  • conf 目录存放的是,配置文件,其包括日志、邮箱、测试环境、数据、数据库信息等等:
    • config.ini 是配置文件,编写方式如下:
[log]
name = frank
level = DEBUG
sh_level = ERROR
fh_level = INFO

[env]
url = http://www.baidu.com
headers = {"Content-Type":"application/x-www-form-urlencoded"}

[test_data]
username = frank
password = 1234567

[db]
host = 118.24.119.60
user = root
password = 123456
database = cms
port = 3306
[email]
  • data 目录存放的是测试用例的excel文件
    • apicase.xlsx 存放的是测试用例,其内容如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4tQa00qz-1607655257199)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20201105155205820.png)]

  • library 包存放的是一些 如生成报告、或者是数据驱动用到的包,内容就不列出:
  • log 目录存放的是日志文件
  • report 目录存放的是报告或者截图等
  • testCase 包存放的就是具体的自动化测试用例
    • 已testLogin.py 模块 来举例,内容如下:
# -*- coding: utf-8 -*-
"""
    ***********************
    @author:   - FrankLee(李学春)
    @time:     - 2020/11/5
    @qq:       - 775729278
    @wechat:   - lxc18286562925
    @FileName: - testLogin.py
    ***********************
"""

import unittest
import os
from common.handlerequests import SendRequest
from common.handlelog import log
from common.readexcel import ReadExcel
from common.handlepath import DATADIR
from common.handleconfig import conf
from library.ddt import data, ddt

casefile = os.path.join(DATADIR, "apicase.xlsx").replace("\\", "/")

"""
    使用ddt装饰类,作数据驱动
"""

@ddt
class TestLogin(unittest.TestCase):
    """
    读取excel的用例
    """
    excel = ReadExcel(casefile, "login")
    cases = excel.readData()

    request = SendRequest()

    """
    使用ddt 的data()函数装饰用例
    接收可变长参数
    """
    @classmethod
    def setUpClass(cls):
        print("测试开始")

    @classmethod
    def tearDownClass(cls):
        print("测试接收")

    @data(*cases)
    def test001Login(self, case):
        """
        1、准备接口请求数据

        :param case:
        :return:
        """
        url = conf.get("env", "url") + case["url"]
        method = case["method"]
        data = eval(case["data"])
        headers = eval(conf.get("env", "headers"))
        expect = eval(case["expected"])
        """
        每执行一次让id加1
        """
        row = case["case_id"] + 1

        """
        发送接口请求
        """
        resp = self.request.send(method, url, data=data, headers=headers)
        dic = eval(resp.text)
        try:
            self.assertEqual(dic, expect, "测试不通过")
        except Exception as e:
            self.excel.writeData(row, 8, "不通过")
            log.error("用例{}执行未通过,case_id为{}".format(case["title"], row))
            raise e
        else:
            self.excel.writeData(row, 8, "通过")
            log.info("用例{}执行通过,case_id为{}".format(case["title"], row))

“”
row = case[“case_id”] + 1

    """
    发送接口请求
    """
    resp = self.request.send(method, url, data=data, headers=headers)
    dic = eval(resp.text)
    try:
        self.assertEqual(dic, expect, "测试不通过")
    except Exception as e:
        self.excel.writeData(row, 8, "不通过")
        log.error("用例{}执行未通过,case_id为{}".format(case["title"], row))
        raise e
    else:
        self.excel.writeData(row, 8, "通过")
        log.info("用例{}执行通过,case_id为{}".format(case["title"], row))

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值