• python自动化测试中装饰器@ddt和@data源码解析


    在这里插入图片描述

    一、使用ddt和data装饰器的大致框架如下,每个test_开头的方法,代表一条测试用例

    from ddt import ddt,data
    import unittest
    
    
    test_datas=[
        {'id':1,'title':'测试用例1'},
        {'id':2,'title':'测试用例2'},
        {'id':3,'title':'测试用例3'},
        {'id':4,'title':'测试用例4'}
    ]
    
    @ddt
    class TestDemo(unittest.TestCase):
    
        @data(*test_datas)
        def test_demo1(self,item):
            print('测试用例执行',item)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    unittest中的测试用例:

    测试类中每一个test开头的方法就是一条测试用例

    ddt根据用例数据生成测试用例的思路:

    1、利用data装饰器:传入测试数据,在装饰器中将测试数据保存起来
    2、ddt这个装饰器:遍历测试数据,每遍历出一条数据,往测试类中添加一个test开头的方法
    setattr(类,方法名,方法)

    二、给类动态的增加方法

    案例1

    setattr(对象/类,属性名/方法名,属性值/方法)

    特别注意:

    给类动态增加方法一定要加self

    class Demo:
    
        def test_1(self):
            print("这个是方法test_1")
    
    
    def kobe(self,item):
        print("kobe-----执行了",item)
    
    datas=[2,8,23,22,24]
    
    #根据数据动态给测试类中增加5个方法
    for i in datas:
        name='test_1_{}'.format(i)
        #给类动态增加方法
        setattr(Demo,name,kobe)
    
    print(Demo.__dict__)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述

    案例2:调用动态执行的5个方法,执行结果都为kobe-----执行了 24,有bug

    class Demo:
    
        def test_1(self):
            print("这个是方法test_1")
    
    
    def kobe(self,item):
        print("kobe-----执行了",item)
    
    datas=[2,8,23,22,24]
    
    #根据数据动态给测试类中增加5个方法
    for i in datas:
        name='test_1_{}'.format(i)
    
        def wrapper(self):
            kobe(self,i)
    
        #给类动态增加方法
        setattr(Demo,name,wrapper)
    
    #print(Demo.__dict__)
    
    Demo().test_1_2()
    Demo().test_1_8()
    Demo().test_1_22()
    Demo().test_1_23()
    Demo().test_1_24()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    执行结果:
    kobe-----执行了 24
    kobe-----执行了 24
    kobe-----执行了 24
    kobe-----执行了 24
    kobe-----执行了 24

    原因分析

    在这里插入图片描述

    案例3:解决案例2的bug

    定义闭包create_method:进行数据锁定,锁定的是datas=[2,8,23,22,24]

    class Demo:
    
        def test_1(self):
            print("这个是方法test_1")
    
    
    def kobe(self,item):
        print("kobe-----执行了",item)
    
    datas=[2,8,23,22,24]
    
    #todo 使用闭包进行数据锁定
    def create_method(i):
        def wrapper(self):
            kobe(self,i)
        return wrapper
    
    
    #根据数据动态给测试类中增加5个方法
    for i in datas:
        name='test_1_{}'.format(i)
    
        wrapper=create_method(i)
    
        #给类动态增加方法
        setattr(Demo,name,wrapper)
    
    Demo().test_1_2()
    Demo().test_1_8()
    Demo().test_1_22()
    Demo().test_1_23()
    Demo().test_1_24()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    三、ddt和data的源码解析

    from ddt import ddt,data
    import unittest
    
    
    test_datas=[
        {'id':1,'title':'测试用例1'},
        {'id':2,'title':'测试用例2'},
        {'id':3,'title':'测试用例3'},
        {'id':4,'title':'测试用例4'}
    ]
    
    
    def ddt(cls):
        '''遍历测试数据,给类动态添加方法'''
        #如何通过类获取方法?
        #res=cls.__dict__
        #print('测试类的方法和属性字典',res)
        for name,method in list(cls.__dict__.items()):
            #遍历出来的属性值(方法)是否拥有datas属性(测试数据)
            if hasattr(method,'datas'):
                #获取方法中保存的测试数据
                datas=getattr(method,'datas')
                #遍历测试数据
                for index,value in enumerate(datas):
                    print("数据:",value)
                    #给测试类动态添加用例
                    method_name='{}_{}'.format(name,index+1)
                    print('方法名',method_name)
    				
    				#给类动态的增加方法
                    def wrapper(self):
                        method(self, value)
    
                    #todo 给测试类动态添加一个测试方法
                    setattr(cls,method_name,wrapper)
    
    
        return cls
    
    def data(*args):
        '''将测试数据保存为测试方法的属性'''
        #*args接收到的是data装饰器传递进来的数据
        def wrapper(func):
            #func接收的是data装饰的函数
            func.datas=args
            return func
    
        return wrapper
    
    
    @ddt
    class TestDemo():
    
        @data(*test_datas)      #test_demo1=data(*test_datas)(test_demo1)
        def test_demo1(self,item):
            print('测试用例执行',item)
    
    #print(TestDemo.test_demo1.__dict__)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    这样写的话有bug
    在这里插入图片描述
    原因:
    在这里插入图片描述

    解决:

    采用闭包进行数据锁定,锁定value和method

    def create_test_method(method,value):
        def wrapper(self):
            method(self, value)
        return wrapper
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    from ddt import ddt,data
    import unittest
    
    
    test_datas=[
        {'id':1,'title':'测试用例1'},
        {'id':2,'title':'测试用例2'},
        {'id':3,'title':'测试用例3'},
        {'id':4,'title':'测试用例4'}
    ]
    
    def create_test_method(method,value):
        def wrapper(self):
            method(self, value)
        return wrapper
    
    def ddt(cls):
        #todo @ddt这个装饰器:遍历测试数据,每遍历出一条数据,往测试类中添加一个test开头的方法
        #setattr(类,方法名,方法)
        res=list(cls.__dict__.items())
        print(res)
        for name,method in res:
            print(name,method)
            if hasattr(method,'datas'):
                #如果有datas属性,获取方法中保存的datas
                datas=getattr(method,'datas')
                #遍历测试数据
                for index,value in enumerate(datas):
                    print('测试数据:',value)
    
                    #给测试类动态的增加测试用例
                    method_name='{}_{}'.format(name,index+1)
                    print('方法:',method_name,method)
    
                    #todo 给类动态的增加方法,最终希望执行def test_demo1(self,item):这个方法的
                    #test_method=method
                    #但是item需要自己传,但是unittest是不需要传递参数的
                    
                    # def wrapper(self):
                    #     method(self,value)
    
                    wrapper=create_test_method(method, value)
    
                    # todo 给测试类动态添加一个测试方法
                    setattr(cls, method_name, wrapper)
                else:
                    delattr(cls,name)
        return cls
    
    def data(*args):
        # *args为给装饰器传递的参数test_datas
    
        def wrapper(func):
            # func为被装饰器装饰的函数test_demo1
            #todo @data装饰器的作用是保存测试数据,将测试数据存放到函数属性中
            func.datas = test_datas
            return func
    
        return wrapper
    
    
    @ddt
    class TestDemo(unittest.TestCase):
    
        @data(*test_datas)      #test_demo1=data(*test_datas)(test_demo1)
        def test_demo1(self,item):
            print('测试用例执行',item)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    分部解析代码

    @data(*test_datas)    
    def test_demo1(self,item):
        print('测试用例执行',item)
    
    • 1
    • 2
    • 3

    1、上面3行代码可以写成如下:

    @data(*test_datas):可以表示为test_demo1=data(*test_datas)(test_demo1)

    2、输出属性(方法)名称和属性值

    for name,method in list(cls.__dict__.items())
    
    • 1

    在这里插入图片描述

    3、将遍历出来的属性名(方法)判断是否包含datas属性,如果有datas属性,获取方法中保存的datas

    if hasattr(method,'datas'):
    	datas=getattr(method,'datas')
    
    • 1
    • 2

    在这里插入图片描述

  • 相关阅读:
    DBCO-PEG-Lectins|二苯并环辛炔-聚乙二醇-凝集素|DBCO-PEG-凝集素
    反射、代理模式、注解
    上传 iOS App ipa安装包 【推荐使用mac】
    剑指offer——JZ82 二叉树中和为某一值的路径(一) 解题思路与具体代码【C++】
    网络安全就业形势怎么样?
    uni-app H5使用 tabbars切换,echartst图表变小 宽度只有100px问题解决
    17 【2D转换 3D转换 浏览器私有前缀】
    JavaScript 中的事件循环是什么?
    jzo3059 雕塑
    Object.entries()
  • 原文地址:https://blog.csdn.net/YZL40514131/article/details/126679826