装饰器重写ddt方法完成数据驱动测试

from unittest import TestCase


def decorator(cls):
    attrs = list(cls.__dict__.items())
    for name, value in attrs:
        if hasattr(value, 'PARAM_DATA'):
            cases = getattr(value, 'PARAM_DATA')
            for index, case in enumerate(cases, start=1):
                # 1.构造测试方法名称
                test_method_name = f'{name}_{index}'
                # 2.获取测试方法的执行逻辑(测试方法引用)
                test_method = value
                # 3.动态设置测试方法
                setattr(cls, test_method_name, test_method)
            delattr(cls, name)

    return cls


def TestData(data_list):
    """
    给测试方法绑定测试数据
    :param data_list:
    :return:
    """
    def wrapper(func):
        setattr(func, 'PARAM_DATA', data_list)
        return func
    return wrapper


@decorator
class TestLogin(TestCase):  # TestLogin = decorator(TestLogin)
    """
    数据驱动测试
    """
    cases1 = [
        {'title': "用例1", "data": 1111},
        {'title': "用例2", "data": 2222},
        {'title': "用例3", "data": 3333},
        {'title': "用例4", "data": 4444}
    ]

    cases2 = [
        {'title': "用例11", "data": 1111},
        {'title': "用例22", "data": 2222},
        {'title': "用例33", "data": 3333},
        {'title': "用例44", "data": 4444}
    ]

    @TestData(cases1)   # TestData(cases1) -> wrapper -> @wrapper ->test_login = wrapper(test_login)
    def test_login(self):
        """
        测试登录接口
        :return:
        """
        print("输入用户名")
        print("输入密码")
        self.assertTrue(1 == 1)

    @TestData(cases2)
    def test_register(self):
        """
        注册接口测试
        :return:
        """
        pass

这段代码是一个 Python 的单元测试相关的程序。它主要用于构建数据驱动的测试用例,使得测试用例可以采用参数化的方式重复执行,实现更全面的测试。

  • 首先,我们定义了一个名为 decorator 的装饰器函数。它接收一个类作为参数,并对类中所有标记了 TestData 装饰器的方法进行处理。具体而言,它会将这些方法的参数列表 PARAM_DATA 展开后进行遍历,对每个参数组合构造一个新的测试方法,并将其添加到原类中的方法列表中。最后,将原有的参数化方法删除。

  • 然后,我们定义了一个 TestData 装饰器函数。它接收一个参数列表 data_list,并返回一个闭包 wrapper。该闭包函数会将接收到的参数列表保存在测试方法的内部属性 PARAM_DATA 中,并将其返回。在被装饰的方法上,TestData 函数的调用会被自动展开为一个闭包函数的调用,并将 data_list 作为参数传入,从而为测试方法动态添加属性 PARAM_DATA

  • 最后,我们定义了一个名为 TestLogin 的测试类,并在其中定义了两个被 TestData 装饰器修饰的测试方法 test_login()test_register()。每个方法都包含一个名为 casesN(N 可以是任意数字)的测试数据列表,用于保存测试方法的参数组合。这些测试数据列表会在 decorator 装饰器函数中被处理,并根据参数组合创建新的测试方法。

我们对以上代码进行断点分析

attrs:
[('__module__', 'test_03_装饰器执行数据驱动测试优化1'), ('__doc__', '\n    数据驱动测试\n    '), ('cases1', [{'title': '用例1', 'data': 1111}, {'title': '用例2', 'data': 2222}, {'title': '用例3', 'data': 3333}, {'title': '用例4', 'data': 4444}]), ('cases2', [{'title': '用例11', 'data': 1111}, {'title': '用例22', 'data': 2222}, {'title': '用例33', 'data': 3333}, {'title': '用例44', 'data': 4444}]), ('test_login', <function TestLogin.test_login at 0x00000242694F1DC0>), ('test_register', <function TestLogin.test_register at 0x00000242694F1EE0>)]
-----------------------------
# 满足条件的name和value
name
'test_login'
-----------------------------
value
<function TestLogin.test_login at 0x000001B3DB641820>
-----------------------------
cases
[{'data': 1111, 'title': '用例1'}, {'data': 2222, 'title': '用例2'}, {'data': 3333, 'title': '用例3'}, {'data': 4444, 'title': '用例4'}]
-----------------------------
test_method_name
'test_login_1'

这段代码实现了数据驱动测试,在 TestCase 类的基础上,通过装饰器 decorator 和装饰器函数 TestData 来为测试方法动态绑定测试数据。

首先,decorator 装饰器函数定义如下:

def decorator(cls):
    """
    用于构造测试方法
    """
    attrs = list(cls.__dict__.items())
    for name, value in attrs:
        if hasattr(value, 'PARAM_DATA'):
            cases = getattr(value, 'PARAM_DATA')
            for index, case in enumerate(cases, start=1):
                # 1.构造测试方法名称
                test_method_name = f'{name}_{index}'
                # 2.获取测试方法的执行逻辑(测试方法引用)
                test_method = value
                # 3.动态设置测试方法
                setattr(cls, test_method_name, test_method)
            delattr(cls, name)

    return cls

其作用是遍历 cls 类中的所有属性,如果某个属性具有 PARAM_DATA 属性,则将其对应的测试数据拆分出来,分别构造新的测试方法,并动态绑定到原有的测试类中。最后,删除原有的测试方法。

接下来,TestData 装饰器函数定义如下:

def TestData(data_list):
    """
    给测试方法绑定测试数据
    :param data_list: 测试数据列表
    :return: 装饰器函数
    """
    def wrapper(func):
        func.PARAM_DATA = data_list     # 为测试方法动态添加属性
        return func
    return wrapper

其作用是为测试方法动态绑定测试数据,具体实现方式是使用闭包,将 data_list 传入到 wrapper 函数中,并将其绑定到测试方法的 PARAM_DATA 属性上。

最后,我们通过在测试类中使用 @TestData 装饰器来动态绑定测试数据,如下所示:

@decorator
class TestLogin(TestCase):
    cases1 = [
        {'title': "用例1", "data": 1111},
        {'title': "用例2", "data": 2222},
        {'title': "用例3", "data": 3333},
        {'title': "用例4", "data": 4444}
    ]
    cases2 = [
        {'title': "用例11", "data": 1111},
        {'title': "用例22", "data": 2222},
        {'title': "用例33", "data": 3333},
        {'title': "用例44", "data": 4444}
    ]

    @TestData(cases1)
    def test_login(self):
        """
        测试登录接口
        :return:
        """
        print("输入用户名")
        print("输入密码")
        self.assertTrue(1 == 1)

    @TestData(cases2)
    def test_register(self):
        """
        注册接口测试
        :return:
        """
        pass

其中,cases1cases2 分别是测试方法 test_logintest_register 对应的测试数据列表。通过在测试方法上方使用 @TestData 装饰器,并传入对应的测试数据列表,来动态绑定测试数据。

最后,通过运行测试类,可以自动生成多组测试用例,每个测试用例都有对应的测试数据。这样就可以大大简化测试代码,提高测试效率。