Package: unittest-seven-helper
unittest_seven_helper基于unittest开发的测试框架，更友好、更灵活的编写、管理与运行测试，生成更加美观的独立单文件HTML报告。内置参数化测试数据存取方案，省去设计的烦恼，节省更多的时间，从而更快的投入到编写用例阶段。notes为方便使用，已更名为stest。该版本相当于stest的1.0.0版，后续的更新只会更新到stest现已支持的功能支持命名测试方法且不与方法的doc string（文档字符串）冲突支持设置测试方法编写人，修改人，最后修改人以及最后一次修改时间等额外记录信息支持设置测试方法的执行顺序支持参数化功能支持数据驱动测试支持设置用例依赖内置参数化数据存取方案(使用excel（xlsx或xls格式）存取和管理维护参数化测试数据，简洁直观，易于修改维护)支持生成更加简洁美观且可作为独立文件发送的HTML测试报告安装pip方式安装pip install unittest_seven_helper源码方式安装(注意以管理员方式执行)python setup.py install执行测试命令行执行python -m unittest_seven_helper -v -html D:\temp\tms_apitest.html calculation_test.py查看命令行参数python -m unittest_seven_helper -h代码中调unittest_seven_helper.main()执行```python
#!/usr/bin/env python
# -*- encoding: utf-8 -*-

import unittest_seven_helper
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()
    unittest_seven_helper.main()
```快速开始导入抽象测试类（AbstractTestCase）和测试方法装饰器（Test）编写继承自AbstractTestCase的测试子类，子类提供以下实用方法collect_testcases()获取类下所有使用Test装饰的enable为True，并根据priority排序后的测试用例对象列表build_self_suite()构建该类测试用例构成的测试套件run_test()执行该类所有使用Test装饰的enable为True，并根据priority排序后的测试用例使用Test标记测试方法。直接调用测试类的run_test()执行测试简单示例#!/usr/bin/env python# -*- encoding: utf-8 -*-fromunittest_seven_helperimportAbstractTestCasefromunittest_seven_helperimportTestastestcasedefget_testdatas(test_class_name,test_method_name,*args,**kwargs):return[[1,2,3],[3,4,7]]classDemo1Test(AbstractTestCase):@testcase(priority=1,enabled=True,data_provider=get_testdatas,author='思文伟',description='两数加法测试01')definteger_addition_02(self,number_1,number_2,expected):result=number_1+number_2self.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'''importosfromunittest_seven_helperimportGLOBAL_CONFIGfromunittest_seven_helperimportAbstractTestCasefromunittest_seven_helperimportTestastestcaseclassDataProvider01(object):defget_testdatas(self,testclass,testmethod,*args,**kwargs):datas=[{'加数1':1,'加数2':2,'预期':3},{'加数1':4,'加数2':5,'预期':9}]returndatasclassDataProvider02(object):defget_testdatas(self,testclass,testmethod,*args,**kwargs):datas=[[{'加数1':7},{'加数2':5},{'预期':12}],[{'加数1':10},{'加数2':5},{'预期':15}]]returndatasTEST_DATA_FILE_DIRPATH=os.path.dirname(os.path.abspath(__file__))# 全局配置 配置默认内置参数数据提供者 测试数据文件所在的目录路径# GLOBAL_CONFIG.seven_data_provider_data_file_dir = r'E:\sw'classCalculationTest(AbstractTestCase):"""数学运算测试"""@classmethoddefsetUpClass(cls):passdefsetUp(self):pass@testcase(priority=1,enabled=True,data_provider=DataProvider01().get_testdatas,author='思文伟',description='整数加法测试01')definteger_addition_01(self,testdata):"""自定义数据提供者 - 测试方法一个参数化示例"""number_1=testdata.get("加数1")number_2=testdata.get("加数2")expected=testdata.get("预期")result=number_1+number_2self.assertEqual(result,expected)@testcase(priority=2,enabled=True,data_provider=DataProvider02().get_testdatas,author='思文伟',description='整数加法测试02')definteger_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_2self.assertEqual(result,expected)@testcase(priority=3,enabled=True,author='思文伟',description='整数减法测试01')definteger_subtraction_01(self):"""不参数化示例"""number_1=21number_2=10result=number_1-number_2expected=11self.assertEqual(result,expected)@testcase(priority=4,enabled=True,author='思文伟',data_provider_kwargs={'data_file_dir_path':TEST_DATA_FILE_DIRPATH},description='整数减法测试02')definteger_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')definteger_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))deftearDown(self):pass@classmethoddeftearDownClass(cls):passif__name__=='__main__':CalculationTest.run_test()Test参数说明参数类型描述author字符串用例编写者editors列表修改者列表dname字符串或列表用于给用例起一个用于设置依赖的名称depends列表用于设置用例依赖，是一个用例依赖列表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)用例依赖设置用例依赖于其它用例成功后执行，如用例所依赖的用例不成功或没有执行，则该用例会被设置为失败。在实际当中，有时会需要用到两个或多个测试用例依赖运行，比如这一种场景：添加和删除设备，如果只有一台设备，那么添加和删除这两个用例就会共用测试数据，就会产生依赖（即：删除设备用例依赖于添加设备用例成功后执行）dname和depends参数使用示例#!/usr/bin/env python# -*- encoding: utf-8 -*-'''@Author: 思文伟@Date: 2021/09/29'''importunittest_seven_helperfromunittest_seven_helperimportAbstractTestCasefromunittest_seven_helperimportTestastestcaseclassDependTest(AbstractTestCase):"""依赖设置测试"""@classmethoddefsetUpClass(cls):passdefsetUp(self):pass@testcase(priority=1,enabled=True,author='思文伟',description='dtest1',depends=['vnctest.py'])defdtest1(self):""" 用例依赖于vnctest.py模块中的所有用例 """pass@testcase(priority=2,enabled=True,author='思文伟',description='dtest2',depends=['vnctest.py.LoginTest'])defdtest2(self):""" 用例依赖于vnctest.py模块中LoginTest类的所有用例 """pass@testcase(priority=2,enabled=True,author='思文伟',description='dtest3',depends=['vnctest.py.LoginTest.login'])defdtest3(self):""" 用例依赖于vnctest.py模块中LoginTest类的login用例 """pass@testcase(priority=2,enabled=True,author='思文伟',description='dtest4',dname='four')defdtest4(self):""" 命名用例为 four """pass@testcase(priority=2,enabled=True,author='思文伟',description='dtest5',depends=['dtest6'])defdtest5(self):""" 用例依赖于当前类的dtest6用例 """pass@testcase(priority=2,enabled=True,author='思文伟',description='dtest6',depends=['four'])defdtest6(self):""" 用例依赖于当前类的命名为four的dtest4用例 """passdeftearDown(self):pass@classmethoddeftearDownClass(cls):passif__name__=='__main__':unittest_seven_helper.main()参数化数据提供者(data provider)测试方法装饰器Test会调用数据提供者(data provider), 传测试类名称和测试方法名称给data provider的前两个固定位置参数, data_provider_args参数传给data provider的变长位置参数，data_provider_kwargs参数传给data provider的变长关键字参数内置参数化数据提供者 - SevenDataProvider实现了参数化测试数据存取方案，使用excel（xlsx或xls格式）存取和管理维护参数化测试数据，简洁直观，易于修改维护。数据在文件中以用例数据块的方式存储。数据块定义：所有行中的第一列是标记列，第一行第一列是数据块开始标记第一行: 用例名称信息(标记列的下一列是用例方法名称列，之后是用例名称列)第二行: 用例数据标题第三行 开始 每一行都是一组完整的测试数据直至遇见空行或者下一个数据块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'}示例classCalculationTest(AbstractTestCase):@classmethoddefsetUpClass(cls):passdefsetUp(self):pass@testcase(priority=4,enabled=True,author='思文伟',data_provider_kwargs={'data_file_dir_path':'E:\\alltest'},description='整数减法测试02')definteger_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')definteger_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))deftearDown(self):pass@classmethoddeftearDownClass(cls):passif__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: 思文伟'''importosfromunittest_seven_helperimportutilsfromunittest_seven_helper.attrs_markerimportAttributeMarkerfromunittest_seven_helper.abstract_data_providerimportAbsractDataProviderfromunittest_seven_helper.excel_file_readerimportTestCaseExcelFileReaderasExcelReaderclassSevenDataProvider(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_NAMEfilename=kwargs.get(param,default_value)ifutils.is_blank_space(filename):raiseValueError("数据文件名必须是字符串类型且不能为空")returnfilenamedef_get_data_file_dir_paht(self,kwargs):param=self.PARAM_DATA_FILE_DIR_PATHifparamnotinkwargs.keys():raiseAttributeError("没有传入数据文件目录")dirpath=kwargs[param]ifutils.is_blank_space(dirpath):raiseValueError("数据文件目录必须是字符串类型且不能为空")returndirpathdef_get_sheet_name_or_index(self,kwargs):returnkwargs.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_nameext=self.FILE_EXTifutils.is_blank_space(data_file_dir_path):raiseValueError("传入的数据文件目录路径不能为空：{}".format(data_file_dir_path))dir_path=data_file_dir_pathifnameandnotutils.is_blank_space(name):full_name=nameifname.endswith(ext)elsename+extelse:raiseValueError("无效数据文件名称：{}".format(name))returnos.path.join(dir_path,full_name)defget_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()forblockindatas_blocks:ifblock.name==test_method_name:forrowinblock.datas:line={}forcellinrow:fortitle,valueincell.items():iftitleinline.keys():continueelse:line[title]=valuedatasets.append(line)breakreturndatasetsclassCalculationTest(AbstractTestCase):@classmethoddefsetUpClass(cls):passdefsetUp(self):pass@testcase(priority=1,enabled=True,data_provider=SevenDataProvider,data_provider_kwargs={'data_file_dir_path':'E:\\mytestdatas'},author='思文伟',description='整数加法测试01')definteger_addition_01(self,testdata):"""自定义数据提供者 - 测试方法一个参数化示例"""number_1=testdata.get("加数1")number_2=testdata.get("加数2")expected=testdata.get("预期")result=number_1+number_2self.assertEqual(result,expected)deftearDown(self):pass@classmethoddeftearDownClass(cls):passif__name__=='__main__':CalculationTest.run_test()非AbsractDataProvider子类数据提供者示例#!/usr/bin/env python# -*- encoding: utf-8 -*-'''@Author: 思文伟'''fromunittest_seven_helperimportAbstractTestCasefromunittest_seven_helperimportTestastestcaseclassDemo1DataProvider(object):defget_testdatas(self,test_class_name,test_method_name,*args,**kwargs):return[[1,2,3],[3,4,7]]classDemo1Test(AbstractTestCase):@testcase(priority=1,enabled=True,data_provider=Demo1DataProvider().get_testdatas,author='思文伟',description='两数加法测试01')definteger_addition_02(self,number_1,number_2,expected):result=number_1+number_2self.assertEqual(result,expected)if__name__=='__main__':Demo1Test.run_test()函数数据提供者示例#!/usr/bin/env python# -*- encoding: utf-8 -*-'''@Author: 思文伟'''fromunittest_seven_helperimportAbstractTestCasefromunittest_seven_helperimportTestastestcasedefget_testdatas(test_class_name,test_method_name,*args,**kwargs):return[[1,2,3],[3,4,7]]classDemo1Test(AbstractTestCase):@testcase(priority=1,enabled=True,data_provider=get_testdatas,author='思文伟',description='两数加法测试01')definteger_addition_02(self,number_1,number_2,expected):result=number_1+number_2self.assertEqual(result,expected)if__name__=='__main__':Demo1Test.run_test()