unitest+excel+requests接口自动化测试框架

前言

采用Excel为数据基础编写一个接口自动化测试框架。

设计流程图

这张图是我的excel接口测试框架的一些设计思路。

框架结构

目录/文件 说明 是否为python包
common 公共类
core 核心类,封装requests
data 测试使用的excel文件存放目录
logs 日志目录
tests 测试用例目录
utils 工具类,如:日志
config.py 配置文件
run.py 执行文件

Excel相关

用例设计

本次依然采用的是智学网登录接口。

配置文件

config.py

#!/usr/bin/env python3
# coding=utf-8
import os


class CF:
    """配置文件"""
    # 项目目录
    BASE_DIR = os.path.abspath(os.path.dirname(__file__))

    # Excel配置
    NUMBER = 0
    NAME = 1
    METHOD = 2
    URL = 3
    ROUTE = 4
    HEADERS = 5
    PARAMETER = 6  # 参数
    EXPECTED_CODE = 7  # 预期响应码
    EXPECTED_REGULAR = 8  # 预期正则
    EXPECTED_VALUE = 9  # 预期结果值
    SPEND_TIME = 10  # 响应时间
    TEST_RESULTS = 11  # 测试结果
    EXTRACT_VARIABLE = 12  # 提取变量

    # 邮箱配置
    EMAIL_INFO = {
        'username': '[email protected]',
        'password': ***,
        'smtp_host': 'smtp.qq.com',
        'smtp_port': 465
    }
    # 收件人
    ADDRESSEE = ['[email protected]']


if __name__ == '__main__':
    print(CF.EXPECTED_CODE)

这里要说明一下为什么Excel配置要从0开始:因为测试用例读取出来之后是个列表,为了方便这里采用0开始。

读取/写入excel

excelset.py

#!/usr/bin/env python
# coding=utf-8
import os
import openpyxl
from config import CF
from openpyxl.styles import Font
from openpyxl.styles import PatternFill


class ExcelSet:
    """Excel配置"""

    def __init__(self):
        self.path = os.path.join(CF.BASE_DIR, 'data', 'usercase.xlsx')
        self.wb = openpyxl.load_workbook(self.path)
        self.table = self.wb.active

    def get_cases(self, min_row=2):
        """获取用例"""
        all_cases = []
        for row in self.table.iter_rows(min_row=min_row):
            all_cases.append((self.table.cell(min_row, CF.NAME + 1).value,
                              min_row, [cell.value for cell in row]))
            min_row += 1
        return all_cases

    def write_results(self, row_n, col_n, value, color=True):
        """写入结果"""
        cell = self.table.cell(row_n, col_n + 1)
        cell.value = value
        font = Font(name='微软雅黑', size=16)
        cell.font = font
        if color:
            if value.lower() in ("fail", 'failed'):
                fill = PatternFill("solid", fgColor="FF0000")
                cell.fill = fill
            elif value.lower() in ("pass", "ok"):
                fill = PatternFill("solid", fgColor="00CD00")
                cell.fill = fill
        self.wb.save(self.path)


if __name__ == '__main__':
    read = ExcelSet()
    print(read.get_cases())

日志封装

logger.py

#!/usr/bin/env python3
# coding=utf-8
import os
import logging
from config import CF
from datetime import datetime


class Logger:
    def __init__(self):
        self.logger = logging.getLogger()
        if not self.logger.handlers:
            self.logger.setLevel(logging.DEBUG)

            # 创建一个handler,用于写入日志文件
            fh = logging.FileHandler(self.log_path, encoding='utf-8')
            fh.setLevel(logging.DEBUG)

            # 创建一个handler,用于输出到控制台
            ch = logging.StreamHandler()
            ch.setLevel(logging.INFO)

            # 定义handler的输出格式
            formatter = logging.Formatter(self.fmt)
            fh.setFormatter(formatter)
            ch.setFormatter(formatter)

            # 给logger添加handler
            self.logger.addHandler(fh)
            self.logger.addHandler(ch)

    @property
    def log_path(self):
        logs_path = os.path.join(CF.BASE_DIR, 'logs')
        if not os.path.exists(logs_path):
            os.makedirs(logs_path)
        now_month = datetime.now().strftime("%Y%m")
        return os.path.join(logs_path, '{}.log'.format(now_month))

    @property
    def fmt(self):
        return '%(levelname)s %(asctime)s %(filename)s:%(lineno)d %(message)s'


log = Logger().logger
if __name__ == '__main__':
    log.info("你好")

正则操作

regular.py

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import re
from utils.logger import log
from common.variables import is_vars
from core.serialize import is_json_str


class Regular:
    """正则类"""

    def __init__(self):
        self.reg = re

    def finds(self, string):
        return self.reg.findall(r'\{{(.*?)}\}', string)

    def subs(self, keys, string):
        result = None
        log.info("提取变量:{}".format(keys))
        for i in keys:
            if is_vars.has(i):
                log.info("替换变量:{}".format(i))
                result = self.reg.sub(r"\{{%s}}" % i, is_vars.get(i), string)
        log.info("替换结果:{}".format(result))
        return result

    def find_res(self, exp, string):
        """在结果中查找"""
        if is_json_str(string):
            return self.reg.findall(r'\"%s":"(.*?)"' % exp, string)[0]
        else:
            return self.reg.findall(r'%s' % exp, string)[0]

定义变量池

variables.py

#!/usr/bin/env python3
# -*- coding:utf-8 -*-


class VariablePool:
    """全局变量池"""

    def get(self, name):
        """获取变量"""
        return getattr(self, name)

    def set(self, name, value):
        """设置变量"""
        setattr(self, name, value)

    def has(self, name):
        return hasattr(self, name)


is_vars = VariablePool()

封装requests

request.py

#!/usr/bin/env python
# coding=utf-8
import urllib3
import requests
from config import CF
from utils.logger import log
from common.regular import Regular
from common.setResult import replace_param
from core.serialize import deserialization
from requests.exceptions import RequestException
from common.variables import is_vars

urllib3.disable_warnings()


class HttpRequest:
    """二次封装requests方法"""

    def __init__(self):
        self.r = requests.session()
        self.reg = Regular()

    def send_request(self, case, **kwargs):
        """发送请求
        :param case: 测试用例
        :param kwargs: 其他参数
        :return: request响应
        """
        if case[CF.URL]:
            is_vars.set('url', case[CF.URL])
        if case[CF.HEADERS]:
            is_vars.set('headers', deserialization(case[CF.HEADERS]))

        method = case[CF.METHOD].upper()
        url = is_vars.get('url') + case[CF.ROUTE]
        self.r.headers = is_vars.get('headers')
        params = replace_param(case)
        if params: kwargs = params
        try:
            log.info("Request Url: {}".format(url))
            log.info("Request Method: {}".format(method))
            log.info("Request Data: {}".format(kwargs))
            if method == "GET":
                response = self.r.get(url, **kwargs)
            elif method == "POST":
                response = self.r.post(url, **kwargs)
            elif method == "PUT":
                response = self.r.put(url, **kwargs)
            elif method == "DELETE":
                response = self.r.delete(url, **kwargs)
            elif method in ("OPTIONS", "HEAD", "PATCH"):
                response = self.r.request(method, url, **kwargs)
            else:
                raise AttributeError("send request method is ERROR!")
            log.info(response)
            log.info("Response Data: {}".format(response.text))
            return response
        except RequestException as e:
            log.exception(format(e))
        except Exception as e:
            raise e


if __name__ == '__main__':
    log.info("你好")

测试操作

test_api.py

扫描二维码关注公众号,回复: 11462122 查看本文章
#!/usr/bin/env python
# coding=utf-8
import unittest
from parameterized import parameterized
from common.excelset import ExcelSet
from core.request import HttpRequest
from common.checkResult import check_result
from common.setResult import get_var_result

excel_set = ExcelSet()


class TestApi(unittest.TestCase):
    """测试接口"""

    @classmethod
    def setUpClass(cls) -> None:
        cls.req = HttpRequest()

    @classmethod
    def tearDownClass(cls) -> None:
        cls.req.r.close()

    @parameterized.expand(excel_set.get_cases())
    def test_api(self, name, number, case):
        """
        测试excel接口用例
        """
        r = self.req.send_request(case)
        get_var_result(r, number, case)
        check_result(r, number, case)


if __name__ == '__main__':
    unittest.main(verbosity=2)

生成测试报告

run.py

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import os
import unittest
from config import CF
from utils.HTMLTestRunner import HTMLTestRunner

test_case = unittest.defaultTestLoader.discover('tests', 'test*.py')


def report_path():
    reports_path = os.path.join(CF.BASE_DIR, 'report')
    if not os.path.exists(reports_path):
        os.makedirs(reports_path)
    return os.path.join(reports_path, 'index.html')


if __name__ == '__main__':
    with open(report_path(), 'wb') as fp:
        runner = HTMLTestRunner(stream=fp,
                                title='Excel接口测试',
                                description="用例执行情况",
                                verbosity=2)
        runner.run(test_case)


测试报告HTMLrunner.py文件来自网络,需要可联系我

Excel的测试结果

失败发送邮件

发送邮件我还没有调试好,等我调试好了在更新发送邮件吧。

猜你喜欢

转载自www.cnblogs.com/wxhou/p/13394677.html