自动化测试框架

# 日志类的封装
import logging
from logging.handlers import RotatingFileHandler
from class_13_0111_rewrite_unittest.config_handle import do_config


class HandleLog:
    """
    日志类的封装
    """
    def __init__(self):
        self.case_logger = logging.getLogger(do_config(‘log‘, ‘logger_name‘))
        self.case_logger.setLevel(do_config(‘log‘, ‘logger_level‘))
        console_handle = logging.StreamHandler()
        file_handle = RotatingFileHandler(filename=do_config(‘log‘, ‘log_filename‘),
                                          maxBytes=do_config(‘log‘, ‘maxBytes‘),
                                          backupCount=do_config(‘log‘, ‘backupCount‘),
                                          encoding=‘utf-8‘)
        console_handle.setLevel(do_config(‘log‘, ‘console_level‘))
        file_handle.setLevel(do_config(‘log‘, ‘file_level‘))
        simple_formatter = logging.Formatter(do_config(‘log‘, ‘simple_formatter‘))
        verbose_formatter = logging.Formatter(do_config(‘log‘, ‘verbose_formatter‘))
        console_handle.setFormatter(simple_formatter)
        file_handle.setFormatter(verbose_formatter)
        self.case_logger.addHandler(console_handle)
        self.case_logger.addHandler(file_handle)

    def get_logger(self):
        """
        获取 logger 日志器对象
        :return:
        """
        return self.case_logger


do_log = HandleLog().get_logger()


if __name__ == ‘__main__‘:
    for _ in range(1000):
        do_log.debug(‘这是 debug 级别的日志。‘)
        do_log.info(‘这是 info 级别的日志。‘)
        do_log.warning(‘这是 warning 级别的日志。‘)
        do_log.error(‘这是 error 级别的日志。‘)
        do_log.critical(‘这是 critical 级别的日志。‘)

日志类的封装

# excel 封装成类
from openpyxl import load_workbook
from collections import namedtuple
from class_13_0111_rewrite_unittest.config_handle import do_config


class HandleExcel(object):
    """
    定义处理 excel 的类
    """
    # config = HandleConfig()  # 定义一个类属性,创建 HandleConfig() 对象

    def __init__(self, filename, sheetname=None):
        self.filename = filename
        self.sheetname = sheetname
        # 定义一个空列表,存放所有 cases 命名元组对象
        self.case_list = []
        # 打开 excel 文件
        self.wb = load_workbook(self.filename)
        # 定位表单
        if self.sheetname is None:  # 如果没有传 sheetname 这个参数,就默认获取第一个表单
            self.ws = self.wb.active
        else:
            self.ws = self.wb[self.sheetname]
        # 取 excel 表头,构造出来一个元组
        self.sheet_head_tuple = tuple(self.ws.iter_rows(max_row=1, values_only=True))[0]
        # 创建一个元组类
        self.cases = namedtuple("cases", self.sheet_head_tuple, rename=True)
        # 三元运算
        # self.ws = self.wb[self.sheetname] if self.sheetname is not None else self.wb.active

    def get_cases(self):
        """
        获取所有的测试用例
        :return: 存放 cases 命名元组的列表
        """
        for row_data in self.ws.iter_rows(min_row=2, values_only=True):
            self.case_list.append(self.cases(*row_data))
        return self.case_list

    def get_case(self, row):
        """
        获取某一条测试用例
        :param row: 行号
        :return: 一个 cases 命名元组对象
        """
        # 判断行号是否符合条件,(数字,和 行号早最大行和最小行之间)
        if isinstance(row, int) and (2 <= row <= self.ws.max_row):
            return tuple(self.ws.iter_rows(min_row=row, max_row=row, values_only=True))[0]
            # return self.case_list[row]  # 这种方法不可取是因为,如果不调用 get_cases ,self.cases_list 就为空
        else:
            print("传入行号参数有误!")

    def write_result(self, row, actual, result):
        """
        将实际值与测试用例执行的结果保存到 excel
        :param row: 行号
        :param actual: 实际值
        :param result: 测试结果: “Pass” or “Fail”
        :return:
        """
        if isinstance(row, int) and (2 <= row <= self.ws.max_row):
            self.ws.cell(row=row, column=do_config(‘excel‘, ‘actual_col‘), value=actual)
            self.ws.cell(row=row, column=do_config(‘excel‘, ‘result_col‘), value=result)
            self.wb.save(self.filename)
        else:
            print("传入行号参数有误!")


do_excel = HandleExcel(do_config(‘file name‘, ‘cases_path‘))

if __name__ == ‘__main__‘:
    # file_name = "cases.xlsx"
    # one_excel = HandleExcel(filename=file_name)
    cases = do_excel.get_cases()
    print(cases)
    one_case = do_excel.get_case(2)
    print(do_excel)
    do_excel.write_result(2, "ces", "测试")

excel处理 类的封装

# 封装配置文件的代码

from configparser import ConfigParser


class HandleConfig(ConfigParser):
    """
    定义处理配置文件的类
    """

    def __init__(self):  # 对父类的构造方法进行拓展
        # 调用父类的构造方法
        super().__init__()  # 重写或者拓展父类的构造方法,往往我们需要先调用父类的构造方法
        self.filename = ‘test_case.conf‘
        self.read(self.filename, encoding=‘utf-8‘)  # 读取配置文件
        # self 是 ConfigParer 的对象,因为 HandleConfig 继承了父类,所以可以直接调用

    def __call__(self, section=‘DEFAULT‘, option=None, is_eval=False, is_bool=False):
        """
        ’对象()‘这种形式,__call__ 方法会被调用
        :param section: 区域名
        :param option: 选项名
        :param is_eval: 为默认参数,是否进行 eval 函数转换,默认不转换
        :param is_bool: 选项所对应的值是否需要转换为 bool 类型,默认不转换
        :return:
        """
        if option is None:
            # 1、一个对象()  -->  返回 DEFAULT 默认区域下的所有选项,构造成一个字典
            # 2、一个对象(区域名)  -->  能够获取此区域下的所有选项,返回一个字典
            return dict(self[section])

        if isinstance(is_bool, bool):
            if is_bool:
                # 3、一个对象(区域名, 选项名,is_bool=True)  -->  将获取到的数据使用 getboolean() 方法来获取
                return self.getboolean(section, option)
        else:
            raise ValueError(‘is_bool 必须是 bool 类型。‘)  # 手动抛出异常

        data = self.get(section, option)
        # 4、一个对象(区域名, 选项名)  -->  通过区域名以及选项名来获取值

        # 5、如果获取到的数据为数字类型的字符串,自动转换为 python 中的 int 类型
        if data.isdigit():  # 判断是否为数字类型的字符串
            return int(data)
        try:
            return float(data)  # 如果为浮点类型的字符串,则直接转换为 float 类型
        except ValueError:
            pass

        if isinstance(is_eval, bool):
            if is_eval:
                # 6、一个对象(is_eval=True)  -->  将获取到的数据使用 eval 函数进行转换
                return eval(data)
        else:
            raise ValueError(‘is_eval 必须是 bool 类型。‘)

        return data


do_config = HandleConfig()


if __name__ == ‘__main__‘:
    config = HandleConfig()
    print(config())
    print(config(‘excel‘))
    a = config(‘excel‘, ‘two_res‘)  # print(config(‘excel‘, ‘actual_col‘))
    print(f‘值为:{a}\n类型为:{type(a)}‘)
    b = config(‘excel‘, ‘two_res‘, is_bool=True)  # print(config(‘excel‘, ‘two_res‘, is_bool=True))
    print(f‘值为:{b}\n类型为:{type(b)}‘)
    c = config(‘excel‘, ‘five_res‘, is_eval=True)
    print(f‘值为:{c}\n类型为:{type(c)}‘)  # print(config(‘excel‘, ‘five_res‘, is_eval=True))

配置文件类的封装

import unittest
import inspect

from ddt import ddt, data
from class_06_1209_unittest_02.math_operation import MathOperation
from openpyxl import load_workbook
from collections import namedtuple

from class_13_0111_rewrite_unittest.excel_handle import do_excel
from class_13_0111_rewrite_unittest.config_handle import do_config
from class_13_0111_rewrite_unittest.log_handle import do_log


@ddt
class TestMulti(unittest.TestCase):
    """
    测试两数相乘
    """
    # handle_config = HandleConfig()
    # handle_excel = HandleExcel(do_config(‘file name‘, ‘cases_path‘))
    cases = do_excel.get_cases()  # 获取所有的用例,返回一个嵌套命名元组的列表

    @classmethod
    def setUpClass(cls) -> None:
        """
        重写父类的类方法
        所有用例执行之前,会被调用一次  -->  整个测试类调用之前,执行一次
        :return:
        """
        do_log.info("{:=^40s}\n".format("开始执行用例!"))

    @classmethod
    def tearDownClass(cls) -> None:
        """
        重写父类的类方法
        所有用例执行之后,会被调用一次  -->  整个测试类调用之后,执行一次
        :return:
        """
        do_log.info("{:=^40s}\n".format("用例执行结束!"))

    @data(*cases)
    def test_multi(self, data_namedtuple):
        """
        两数相乘
        :return:
        """
        # 能够通过 inspect.stack 方法查看当前运行的实例名称
        do_log.info(f"Running Test Method: {inspect.stack()[0][3]}\n")

        case_id = data_namedtuple.case_id
        msg = data_namedtuple.title
        l_date = data_namedtuple.l_date
        r_date = data_namedtuple.r_date
        expected = data_namedtuple.expected

        # 获取实际结果
        real_result = MathOperation(l_date, r_date).mul()
        # 预期结果
        expect_result = expected
        # msg
        run_success_msg = do_config(‘msg‘, ‘success_result‘)
        run_fail_msg = do_config(‘msg‘, ‘fail_result‘)
        # 断言
        try:
            self.assertEqual(expect_result, real_result, msg="{}失败!".format(msg))
        except AssertionError as e:
            # 记录日志
            do_log.error("{},执行结果为:{}\n具体异常为:{}\n".format(msg, run_fail_msg, e))  # 将执行失败的信息写入到文件
            do_excel.write_result(row=case_id+1, actual=real_result, result=run_fail_msg)
            raise e
        else:
            # self.file.write("{},执行结果为:{}\n".format(msg, run_success_msg))  # 将执行成功的信息写入到文件
            do_log.info("{},执行结果为:{}\n".format(msg, run_success_msg))
            do_excel.write_result(row=case_id+1, actual=real_result, result=run_success_msg)


if __name__ == ‘__main__‘:
    unittest.main()

测试脚本

# 注释不要和区域名和选项名写在一行

# 文件路径配置
[file name]
# cases_path 为测试用例 excel 文件的路径
# log_path 为记录日志的文件路径
cases_path = cases.xlsx
log_path = run_result_1.txt
report_html_name = test_result

# 提示信息
[msg]
; success_result 为用例执行成功的提示信息
; fail_path 为用例执行失败的提示信息
success_result = Pass
fail_result = Fail

# excel 相关配置
[excel]
# actual_col 为将用例执行的实际结果存储到 excel 中的列号
# result_col 为将用例执行的结果存储到 excel 中的列号
actual_col = 6
result_col = 7

# 日志相关配置
[log]
# 日志器名称
logger_name = case
# 日志器日志等级
logger_level = DEBUG
# 日志文件名
log_filename = case.log
# 一个日志文件的最大字节数
maxBytes = 4096
# 备份文件数
backupCount = 3
# 输出到控制台的日志等级
console_level = ERROR
# 输出到文件的日志等级
file_level = INFO
# 控制台使用的日志简单格式
# 配置文件出现 % ,需要使用 % 来转义
simple_formatter = %%(asctime)s - [%%(levelname)s] - [日志信息]:%%(message)s
# 日志文件使用日志复杂格式
verbose_formatter = %%(asctime)s - [%%(levelname)s] - %%(module)s - %%(name)s - %%(lineno)d - [日志信息]:%%(message)s

配置文件

相关推荐