ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

unittest测试增强库unittest_seven_helper

2021-04-28 23:31:55  阅读:218  来源: 互联网

标签:seven name helper get self number provider data unittest


简介

unittest只是提供了基础的架构,功能较少,测试方法命名也不够灵活,特别是不支持参数化和数据驱动 难以满足现代的需求(控制执行顺序,参数化和数据驱动等),故此开发了该测试增强库。更友好、更灵活的编写、管理与运行测试,生成更加美观的独立单文件HTML报告。内置参数化测试数据存取方案,省去设计的烦恼,节省更多的时间,从而更快的投入到编写用例阶段。

  • 现已支持的功能

    • 支持命名测试方法且不与方法的doc string(文档字符串)冲突
    • 支持设置测试方法编写人,修改人,最后修改人以及最后一次修改时间等额外记录信息
    • 支持设置测试方法的执行优先级
    • 支持参数化功能
    • 支持数据驱动测试
    • 内置参数化数据存取方案(使用excel(xlsx格式)存取和管理维护参数化测试数据,简洁直观,易于修改维护)
  • 下一步将支持功能

    • 生成HTML报告
      类似我开发的自动化测试平台的这种HTML报告
      在这里插入图片描述
  • email:hotswwkyo@qq.com

  • github:https://github.com/hotswwkyo/unittest_seven_helper

安装

pip方式安装

pip install unittest_seven_helper

源码方式安装(注意以管理员方式执行)

python setup.py install

快速开始

  1. 导入抽象测试类(AbstractTestCase)和测试方法装饰器(Test)
  2. 编写继承自AbstractTestCase的测试子类,子类提供以下实用方法
    • collect_testcases()

      获取类下所有使用Test装饰的enable为True,并根据priority排序后的测试用例对象列表

    • build_self_suite()

      构建该类测试用例构成的测试套件

    • run_test()

      执行该类所有使用Test装饰的enable为True,并根据priority排序后的测试用例

  3. 使用Test标记测试方法。
  4. 直接调用测试类的run_test()执行测试
  • 简单示例

    #!/usr/bin/env python
    # -*- encoding: utf-8 -*-
    
    from unittest_seven_helper import AbstractTestCase
    from unittest_seven_helper import Test as testcase
    
    
    def get_testdatas(test_class_name, test_method_name, *args, **kwargs):
    
        return [[1,2,3], [3,4,7]]
    
    
    class Demo1Test(AbstractTestCase):
    
        @testcase(priority=1, enabled=True, data_provider=get_testdatas, author='思文伟', description='两数加法测试01')
        def integer_addition_02(self, number_1, number_2, expected):
    
            result = number_1 + number_2
            self.assertEqual(result, expected)
    if __name__ == '__main__':
        Demo1Test.run_test()
    
  • 综合示例(来自源码包下的samples/calculation_test.py)

    #!/usr/bin/env python
    # -*- encoding: utf-8 -*-
    '''
    @Author: 思文伟
    @Date: 2021/03/30 15:49:32
    '''
    import os
    
    from unittest_seven_helper import GLOBAL_CONFIG
    from unittest_seven_helper import AbstractTestCase
    from unittest_seven_helper import Test as testcase
    
    
    class DataProvider01(object):
        def get_testdatas(self, testclass, testmethod, *args, **kwargs):
    
            datas = [
                {'加数1':1,'加数2':2,'预期':3},
                {'加数1':4,'加数2':5,'预期':9}
            ]
            return datas
    
    class DataProvider02(object):
        def get_testdatas(self, testclass, testmethod, *args, **kwargs):
    
            datas = [
                [{'加数1':7}, {'加数2':5}, {'预期':12}],
                [{'加数1':10}, {'加数2':5}, {'预期':15}]
            ]
            return datas
    
    TEST_DATA_FILE_DIRPATH = os.path.dirname(os.path.abspath(__file__))
    
    # 全局配置 配置默认内置参数数据提供者 测试数据文件所在的目录路径
    # GLOBAL_CONFIG.seven_data_provider_data_file_dir = r'E:\sw'
    
    
    class CalculationTest(AbstractTestCase):
        """数学运算测试"""
        @classmethod
        def setUpClass(cls):
            pass
    
        def setUp(self):
            pass
    
        @testcase(priority=1, enabled=True, data_provider=DataProvider01().get_testdatas, author='思文伟', description='整数加法测试01')
        def integer_addition_01(self, testdata):
            """自定义数据提供者 - 测试方法一个参数化示例"""
    
            number_1 = testdata.get("加数1")
            number_2 = testdata.get("加数2")
            expected = testdata.get("预期")
    
            result = number_1 + number_2
            self.assertEqual(result, expected)
    
        @testcase(priority=2, enabled=True, data_provider=DataProvider02().get_testdatas, author='思文伟', description='整数加法测试02')
        def integer_addition_02(self, testdata_01, testdata_02, testdata_03):
            """自定义数据提供者 - 测试方法多个参数化示例"""
    
            number_1 = testdata_01.get("加数1")
            number_2 = testdata_02.get("加数2")
            expected = testdata_03.get("预期")
    
            result = number_1 + number_2
            self.assertEqual(result, expected)
    
        @testcase(priority=3, enabled=True, author='思文伟', description='整数减法测试01')
        def integer_subtraction_01(self):
            """不参数化示例"""
    
            number_1 = 21
            number_2 = 10
            result = number_1 - number_2
            expected = 11
            self.assertEqual(result, expected)
    
        @testcase(priority=4, enabled=True, author='思文伟', data_provider_kwargs={'data_file_dir_path':TEST_DATA_FILE_DIRPATH}, description='整数减法测试02')
        def integer_subtraction_02(self, testdata):
            """使用内置的数据提供者 - 传入测试数据文件所在的目录路径"""
    
            number_1 = testdata.get("减数1")
            number_2 = testdata.get("减数2")
            expected = testdata.get("预期")
    
            result = int(number_1) - int(number_2)
            self.assertEqual(result, int(expected))
    
        @testcase(priority=5, enabled=True, author='思文伟', description='整数减法测试03')
        def integer_subtraction_03(self,testdata):
            """使用内置的数据提供者 - 不传入测试数据文件所在的目录路径,
            则会检测GLOBAL_CONFIG.seven_data_provider_data_file_dir 是否设置
            ,没有设置则会使用该方法所属的测试类所在的模块目录路径作为测试数据文件的查找目录
            """
    
            number_1 = testdata.get("减数1")
            number_2 = testdata.get("减数2")
            expected = testdata.get("预期")
    
            result = int(number_1) - int(number_2)
            self.assertEqual(result, int(expected))
    
        def tearDown(self):
            pass
    
        @classmethod
        def tearDownClass(cls):
            pass
    
    if __name__ == '__main__':
        CalculationTest.run_test()
    
    

Test参数说明

参数类型描述
author字符串用例编写者
editors列表修改者列表
groups列表方法所属的组的列表 — 用于后续的设置组依赖功能
enabled布尔值是否启用执行该测试方法
priority整数测试方法的执行优先级,数值越小执行越靠前
alway_run布尔值如果设置为true,则此测试方法将始终运行,即使它依赖于失败的方法也是如此 — 该功能暂未实现
description字符串测试用例名称
data_providerobject测试方法的参数化数据提供者,默认值是None,AbsractDataProvider的子类或者一个可调用的对象,返回数据集列表(当测试方法只有一个参数化时,应返回一维列表,多个参数化时返回二维列表)
data_provider_args元祖数据提供者变长位置参数(args)
data_provider_kwargs字典数据提供者变长关键字参数(kwargs)
last_modifyied_by字符串最后修改者
last_modified_time字符串最后一次修改的时间
enable_default_data_provider布尔值是否使用内置数据提供者(SevenDataProvider),默认值是True,未设置data_provider,且该值为True 才会使用内置数据提供者(SevenDataProvider)
depends_on_groups列表此方法所依赖的组列表 — 该功能暂未实现
depends_on_methods列表此方法所依赖的方法列表 — 该功能暂未实现

参数化数据提供者(data provider)

测试方法装饰器Test会调用数据提供者(data provider), 传测试类名称和测试方法名称给data provider的前两个固定位置参数, data_provider_args参数传给data provider的变长位置参数,data_provider_kwargs参数传给data provider的变长关键字参数

内置参数化数据提供者 - SevenDataProvider

实现了参数化测试数据存取方案,使用excel(xlsx格式)存取和管理维护参数化测试数据,简洁直观,易于修改维护。数据在文件中以用例数据块的方式存储。

  • 数据块定义:

    • 所有行中的第一列是标记列,第一行第一列是数据块开始标记
    • 第一行: 用例名称信息(标记列的下一列是用例方法名称列,之后是用例名称列)
    • 第二行: 用例数据标题
    • 第三行 开始 每一行都是一组完整的测试数据直至遇见空行或者下一个数据块
      在这里插入图片描述
  • kwargs变长关键字参数接收参数:

    • data_file_name - 数据文件名称
    • data_file_dir_path - 数据文件所在目录路径
    • sheet_name_or_index - 数据文件中数据所在的工作表索引(从0开始)或名称
  • 返回值
    测试数据行信息字典构成的一维列表, 如:

    [{“减数1”: “36”, “减数2”: “10”, “预期”: “26”}, {“减数1”: “57”, “减数2”: “30”, “预期”: “27”}]
    在这里插入图片描述

  • 使用
    框架是默认启用内置的数据提供者(SevenDataProvider)所以不需要做任何设置,返回值是测试数据行信息字典构成的一维列表,所以测试方法统一接收一个参数化参数

    • 启用条件

      • 测试方法装饰器Test参数enable_default_data_provider 为True,默认值是True
      • 测试方法装饰器Test参数data_provider 为None(即未设置数据提供者),默认值是True为None
    • 数据文件存放目录
      通过测试方法装饰器Test参数data_provider_kwargs传入data_file_dir_path,如果没有传入,则会去检查全局配置unittest_seven_helper.GLOBAL_CONFIG.seven_data_provider_data_file_dir是否设置,设置了则取该值作为参数化测试数据文件的查找目录,否则以被装饰的测试方法所在的模块目录作为查找目录

      data_provider_kwargs={‘data_file_dir_path’:‘E:\mytestdatas’}

    • 数据文件名
      通过测试方法装饰器Test参数data_provider_kwargs传入data_file_name,如果没有传入,则取测试方法所属的测试类名作为测试数据文件名称

      data_provider_kwargs={‘data_file_name’:‘mytest’}

  • 示例

class CalculationTest(AbstractTestCase):
        @classmethod
        def setUpClass(cls):
            pass

        def setUp(self):
            pass

        @testcase(priority=4, enabled=True, author='思文伟', data_provider_kwargs={'data_file_dir_path':'E:\\alltest'}, description='整数减法测试02')
        def integer_subtraction_02(self, testdata):
            """使用内置的数据提供者 - 传入测试数据文件所在的目录路径"""
            
            number_1 = testdata.get("减数1")
            number_2 = testdata.get("减数2")
            expected = testdata.get("预期")
            
            result = int(number_1) - int(number_2)
            self.assertEqual(result, int(expected))
            
        @testcase(priority=5, enabled=True, author='思文伟', description='整数减法测试03')
        def integer_subtraction_03(self,testdata):
            """使用内置的数据提供者 - 不传入测试数据文件所在的目录路径, 
            则会检测GLOBAL_CONFIG.seven_data_provider_data_file_dir 是否设置
            ,没有设置则会使用该方法所属的测试类所在的模块目录路径作为测试数据文件的查找目录
            """
            
            number_1 = testdata.get("减数1")
            number_2 = testdata.get("减数2")
            expected = testdata.get("预期")
            
            result = int(number_1) - int(number_2)
            self.assertEqual(result, int(expected))

        def tearDown(self):
            pass

        @classmethod
        def tearDownClass(cls):
            pass

    if __name__ == '__main__':
        CalculationTest.run_test()

自定义参数化数据提供者

自定义参数化数据提供者,可以是AbsractDataProvider的子类或者一个可调用的对象,返回数据集列表(当测试方法只有一个参数化时,应返回一维列表,多个参数化时返回二维列表),必须接收两个固定位置参数,变长位置参数(args)和变长关键字参数(kwargs),固定位置参数,第一个是测试类名,第二个是测试方法名。

  • 返回值

    返回测试方法的参数化测试数据列表

    • 测试方法只有一个参数化时, 返回一维列表 如: demotest(self, testdata), data provider 返回 [{‘name’:‘zhansan’, ‘age’:17}, {‘name’:‘xiaoming’, ‘age’:18}],方法demotest会执行两次,第一次参数testdata是:{‘name’:‘zhansan’, ‘age’:17},
      第二次则是:{‘name’:‘xiaoming’, ‘age’:18}
    • 测试方法有多个参数化时,返回二维列表 如: demotest(self, name, age), data provider 返回 [[‘zhansan’, 17], [‘xiaoming’, 18]], 方法demotest会执行两次,第一次参数name和age的值分别是:‘zhansan’, 18,
      第二次则是:‘xiaoming’, 18
  • 实现方式

    • 继承AbsractDataProvider,实现get_testdatas(self, test_class_name, test_method_name, *args, **kwargs)方法
    • 其他类型的类似接收以下参数的可调用对象 ------> get_testdatas(test_class_name, test_method_name, *args, **kwargs)
  • 使用

    通过测试方法装饰器Test参数data_provider来设置为自己的数据提供者(data provider), 参数data_provider_args和data_provider_kwargs分别用来传给数据提供者(data provider)对应的变长位置参数(args)和变长关键字参数(kwargs)

  • 示例

    继承自AbsractDataProvider的数据提供者示例(来自内置数据提供者 - SevenDataProvider)

    #!/usr/bin/env python
    # -*- encoding: utf-8 -*-
    '''
    @Author: 思文伟
    '''
    
    import os
    from unittest_seven_helper import utils
    from unittest_seven_helper.attrs_marker import AttributeMarker
    from unittest_seven_helper.abstract_data_provider import AbsractDataProvider
    from unittest_seven_helper.excel_file_reader import TestCaseExcelFileReader as ExcelReader
    
    
    class SevenDataProvider(AbsractDataProvider):
    
        FILE_EXT = AttributeMarker(".xlsx", True, "数据文件拓展名")
        BLOCK_FLAG = AttributeMarker("用例名称", True, "用例分隔标记")
        DEFAULT_SHEET_INDEX = AttributeMarker(0, True, "默认从索引为0的工作表读取数据")
    
        # get_datasets方法变长字典参数kwargs接收的参数的键名
        PARAM_DATA_FILE_NAME = AttributeMarker("data_file_name", True, "数据文件名称参数")
        PARAM_DATA_FILE_DIR_PATH = AttributeMarker("data_file_dir_path", True, "数据文件所在目录路径参数")
        PARAM_SHEET_NAME_OR_INDEX = AttributeMarker("sheet_name_or_index", True, "数据文件中数据所在的工作表索引(从0开始)或名称参数")
        KWARGS_NAMES = AttributeMarker((PARAM_DATA_FILE_NAME, PARAM_DATA_FILE_DIR_PATH, PARAM_SHEET_NAME_OR_INDEX), True, "接收的参数名")
    
        def _get_data_file_name(self, kwargs, default_value=None):
    
            param = self.PARAM_DATA_FILE_NAME
            filename = kwargs.get(param, default_value)
            if utils.is_blank_space(filename):
                raise ValueError("数据文件名必须是字符串类型且不能为空")
            return filename
    
        def _get_data_file_dir_paht(self, kwargs):
    
            param = self.PARAM_DATA_FILE_DIR_PATH
            if param not in kwargs.keys():
                raise AttributeError("没有传入数据文件目录")
            dirpath = kwargs[param]
            if utils.is_blank_space(dirpath):
                raise ValueError("数据文件目录必须是字符串类型且不能为空")
            return dirpath
    
        def _get_sheet_name_or_index(self, kwargs):
            return kwargs.get(self.PARAM_SHEET_NAME_OR_INDEX, self.DEFAULT_SHEET_INDEX)
    
        def _build_file_full_path(self, data_file_dir_path, data_file_name):
            """构建完整的excel数据文件路径
    
            Args:
                data_file_dir_path: 文件目录
                data_file_name: 文件名称
            """
    
            name = data_file_name
            ext = self.FILE_EXT
            if utils.is_blank_space(data_file_dir_path):
                raise ValueError("传入的数据文件目录路径不能为空:{}".format(data_file_dir_path))
            dir_path = data_file_dir_path
            if name and not utils.is_blank_space(name):
                full_name = name if name.endswith(ext) else name + ext
            else:
                raise ValueError("无效数据文件名称:{}".format(name))
            return os.path.join(dir_path, full_name)
    
        def get_testdatas(self, test_class_name, test_method_name, *args, **kwargs):
            """根据文件名从指定的excel文件(xlsx文件格式)读取出数据, 返回一维列表,每个元素是excel表中一行测试数据信息字典.
            eg: [{"减数1": "36", "减数2": "10", "预期": "26"}, {"减数1": "57", "减数2": "30", "预期": "27"}]
    
            Args:
                kwargs:
                    file_name 数据文件名, 不提供则测试类名称作为文件名
                    file_dir_path 数据文件所在目录路径
                    sheet_index_or_name Excel工作表索引(从0开始)或名称,不提供则默认取索引0的工作表
            """
    
            datasets = []
    
            filename = self._get_data_file_name(kwargs, test_class_name)
            dirpath = self._get_data_file_dir_paht(kwargs)
            full_file_path = self._build_file_full_path(dirpath, filename)
    
            reader = ExcelReader(full_file_path, testcase_block_separators=self.BLOCK_FLAG, sheet_index_or_name=self._get_sheet_name_or_index(kwargs))
            datas_blocks = reader.load_testcase_data()
            for block in datas_blocks:
                if block.name == test_method_name:
                    for row in block.datas:
                        line = {}
                        for cell in row:
                            for title, value in cell.items():
                                if title in line.keys():
                                    continue
                                else:
                                    line[title] = value
                        datasets.append(line)
                    break
            return datasets
    
    
    class CalculationTest(AbstractTestCase):
        @classmethod
        def setUpClass(cls):
            pass
    
        def setUp(self):
            pass
    
        @testcase(priority=1, enabled=True, data_provider=SevenDataProvider, data_provider_kwargs={'data_file_dir_path':'E:\\mytestdatas'}, author='思文伟', description='整数加法测试01')
        def integer_addition_01(self, testdata):
            """自定义数据提供者 - 测试方法一个参数化示例"""
    
            number_1 = testdata.get("加数1")
            number_2 = testdata.get("加数2")
            expected = testdata.get("预期")
            
            result = number_1 + number_2
            self.assertEqual(result, expected)
    
        def tearDown(self):
            pass
    
        @classmethod
        def tearDownClass(cls):
            pass
    
    if __name__ == '__main__':
        CalculationTest.run_test()
    
    

    非AbsractDataProvider子类数据提供者示例

    #!/usr/bin/env python
    # -*- encoding: utf-8 -*-
    '''
    @Author: 思文伟
    '''
    
    from unittest_seven_helper import AbstractTestCase
    from unittest_seven_helper import Test as testcase
    
    
    class Demo1DataProvider(object):
    
        def get_testdatas(self, test_class_name, test_method_name, *args, **kwargs):
    
            return [[1,2,3],[3,4,7]]
    
    
    class Demo1Test(AbstractTestCase):
    
        @testcase(priority=1, enabled=True, data_provider=Demo1DataProvider().get_testdatas, author='思文伟', description='两数加法测试01')
        def integer_addition_02(self, number_1, number_2, expected):
    
            result = number_1 + number_2
            self.assertEqual(result, expected)
    
    if __name__ == '__main__':
    
        Demo1Test.run_test()
    

    函数数据提供者示例

    #!/usr/bin/env python
    # -*- encoding: utf-8 -*-
    '''
    @Author: 思文伟
    '''
    
    from unittest_seven_helper import AbstractTestCase
    from unittest_seven_helper import Test as testcase
    
    
    def get_testdatas(test_class_name, test_method_name, *args, **kwargs):
    
        return [[1,2,3], [3,4,7]]
    
    
    class Demo1Test(AbstractTestCase):
    
        @testcase(priority=1, enabled=True, data_provider=get_testdatas, author='思文伟', description='两数加法测试01')
        def integer_addition_02(self, number_1, number_2, expected):
    
            result = number_1 + number_2
            self.assertEqual(result, expected)
    
    if __name__ == '__main__':
    
        Demo1Test.run_test()
    

标签:seven,name,helper,get,self,number,provider,data,unittest
来源: https://blog.csdn.net/hotswwkyo/article/details/116204188

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有