DDT is easy to use but can you really use it? 30 lines of code to implement DDT by hand, which shocked colleagues' jaws!

Most of the friends who have done automation with python should have used the ddt module. It is undeniable that the ddt module is really easy to use. It can automatically generate test cases based on use case data, and it is very convenient to combine test data and test cases. Execution logic is separated.

Next, I will take everyone to create a ddt together:

foreword

1. The realization principle of DDT

First, let's take a look at the basic use of ddt:

picture

ddt is very concise when used, that is, two decorators, the @ddt decorator decorates the test class, and the @data decorator decorates the use case method and passes in the test data. The effect achieved by these two decorators is to automatically generate use cases based on the incoming use case data.

How is it achieved? In fact, the idea of ​​​​realization is also very simple, just two steps:

Step 1: Save the incoming use case data

Step 2: Traverse the use case data, and dynamically add a use case method to the test class every time a piece of data is traversed.

The two decorators in ddt actually implement these two steps:

@data: The first step is to save the incoming test data;

What @ddt does is the second step, traversing the use case data and dynamically adding use case methods to the test class.

If you want to learn automated testing, here I recommend a set of videos for you. This video can be said to be the number one automated testing tutorial on the entire network at station B. At the same time, the number of people online has reached 1,000, and there are notes to collect and share with you. Dashen Technical Exchange: 798478386   

[Updated] The most detailed collection of practical tutorials for automated testing of Python interfaces taught by station B (the latest version of actual combat)_哔哩哔哩_bilibili [Updated] The most detailed collection of practical tutorials for automated testing of Python interfaces taught by station B (actual combat) The latest version) has a total of 200 videos, including: 1. Why should interface automation be done in interface automation, 2. The overall view of request in interface automation, 3. Interface combat in interface automation, etc. UP hosts more exciting videos, please pay attention to UP account . https://www.bilibili.com/video/BV17p4y1B77x/?spm_id_from=333.337.search-card.all.click

2. Implementation of data decorator

Earlier we talked about the data decorator, what it does is to save the use case data.

So how to save it? In fact, the easiest way is to save the properties of the decorated use case method.

Next, let's implement it in detail:

First look at a case of ddt usage

@ddt
class TestLogin(unittest.TestCase):

    @data(11,22)
    def test_login(self, item):
      pass

 Friends who have understood the principle of decorator decorators should know that the execution effect of the above line of code @data(11,22) is equivalent to

test_login = data(11,22)(test_login)

Next, let's analyze the above line of code. First, call the decorator function data, pass in the use case data 11, 22 as parameters, then return a callable object (function), call the returned function again and pass the use case method passed in. After clarifying the calling process, we can define the data decorator function in combination with the previous requirements.

The specific implementation is as follows:

def data(*args):
    def wrapper(func):
        setattr(func, "PARAMS", args)
        return func
    return wrapper

Code interpretation:

When using data in the previous case, the executed test_login = data(11,22)(test_login)
First call the 11,22 passed in by data to receive through the variable length parameter args, and then return the nested function wrapper
Then call the returned wrapper function and pass in the decorated test_login method
In the wrapper function, we save the use case data as the PARAMS attribute of the test_login method, and then return test_login
So far, we have implemented the storage of use case data for the data decorator

3. Implementation of ddt decorator

After saving the use case data through the data decorator, we next implement the ddt decorator to generate test cases based on the use case data. In the previous case, when @ddt decorated the test class, the actual execution effect was equivalent to the following code

TestLogin = ddt(TestLogin)
TestLogin = ddt(TestLogin)

This line of code is to pass the decorated class into the ddt decorator function, and then assign the return value to TestLogin. When we analyzed before, we said that the ddt decorator does is to traverse the use case data and dynamically add use case methods to the test class.

Next, let's implement the logic inside the ddt decorator.

def ddt(cls):
    for name, func in list(cls.__dict__.items()):
        if hasattr(func, "PARAMS"):
            for index, case_data in enumerate(getattr(func, "PARAMS")):
                new_test_name ="{}_{}".format(name,index)
                setattr(cls, new_test_name, func)
            else:
                delattr(cls, name)
    return cls

Code interpretation:

The internal logic description of the ddt function:
1. When calling the ddt function, the test class will be passed in as a parameter.
2. Then  cls.__dict__ traverse by getting all the properties and methods of the test
3. Determine whether the attribute or method from the variable has the attribute PARAMS,
4. If there is, it means that this method has been decorated with data decorator and passed in the use case data.
5. Obtain all use case data through getattr(func, "PARAMS") and traverse.
6. Every time a set of use case data is traversed, a use case method name is generated, and then a use case method is dynamically added to the test class.
7. After traversing all the use case data, delete the test method originally defined by the test class
8. Finally, return to the test class

When the basic functions of the two decorator functions ddt and data have been realized so far, test cases can be automatically generated based on the use case data. Next, we write a test class to check

# 定义装饰器函数data
def data(*args):
    def wrapper(func):
        setattr(func, "PARAMS", args)
        return func

    return wrapper

# 定义装饰器函数ddt
def ddt(cls):
    for name, func in list(cls.__dict__.items()):
        if hasattr(func, "PARAMS"):
            for index, case_data in enumerate(getattr(func, "PARAMS")):
                new_test_name = "{}_{}".format(name, index)
                setattr(cls, new_test_name, func)
            else:
                delattr(cls, name)
    return cls


import unittest

# 编写测试类
@ddt
class TestDome(unittest.TestCase):
    @data(11, 22, 33, 44)
    def test_demo(self):
        pass

Running the above use cases, we will find that four use cases have been executed, and the function of generating use cases based on use case data has been realized .

4. Solve the problem of use case parameter passing

Although the above basic functions have been realized, there is still a problem: the data of the use case is not passed to the use case method . So how to realize the use case data transfer, we can modify the use case method through a closure function, so that when the use case method is called, the use case test is passed in as a parameter.

Modify the function code of the original use case method as follows

from functools import wraps

def update_test_func(test_func,case_data):
    @wraps(test_func)
    def wrapper(self):
        return test_func(self, case_data)
    return wrapper

Code interpretation:

Above we defined a closure function called update_test_func
The closure function receives two parameters: test_func (receiving use case method), case_data (receiving use case data)
The closure function returns a nested function, and the nested function internally calls the original use case method and passes in the test data
When the nested function is defined, it is decorated with the decorator wraps in the functools module, which allows the nested function wrapper to have the relevant attributes of the use case function test_func.

Let's go back to the ddt function we wrote earlier. Before adding a test case to the test class, call the update_test_func method to modify the use case method:

def ddt(cls):
    for name, func in list(cls.__dict__.items()):
        if hasattr(func, "PARAMS"):
            for index, case_data in enumerate(getattr(func, "PARAMS")):
                # 生成一个用例方法名
                new_test_name = "{}_{}".format(name, index)
                # 修改原有的测试方法,设置用例数据为测试方法的参数
                test_func = update_test_func(func,case_data)
                setattr(cls, new_test_name, test_func)
            else:
                delattr(cls, name)
    return cls

After adding this step, the test methods we dynamically add to the test class in the test class actually point to the wrapper function defined in update_test_func, which is actually the wrapper function that is executed when the test is executed, and in the wrapper Inside the function, we call the originally defined test method and pass in the use case data.

So far we have fully realized the function of ddt!

End:

Let me give you a complete case. You can copy the past and run it, or you can write it yourself, and you can also customize the extension according to your own needs.

complete case


from functools import wraps
import unittest

# --------ddt的实现--------
def data(*args):
    def wrapper(func):
        setattr(func, "PARAMS", args)
        return func

    return wrapper


def update_test_func(test_func, case_data):
    @wraps(test_func)
    def wrapper(self):
        return test_func(self, case_data)

    return wrapper


def ddt(cls):
    for name, func in list(cls.__dict__.items()):
        if hasattr(func, "PARAMS"):
            for index, case_data in enumerate(getattr(func, "PARAMS")):
                # 生成一个用例方法名
                new_test_name = "{}_{}".format(name, index)
                # 修改原有的测试方法,设置用例数据为测试方法的参数
                test_func = update_test_func(func, case_data)
                setattr(cls, new_test_name, test_func)
            else:
                delattr(cls, name)
    return cls

# --------测试用例编写--------
@ddt
class TestDome(unittest.TestCase):
    @data(11, 22, 33, 44)
    def test_demo(self, data):
        assert data < 40
#---------用例执行-----------
unittest.main()

If you don't understand something after reading the article, you can learn about the test development course of our lemon class. After learning the decorator in the test development course, the class will teach how to implement ddt.

Of course, the realization of ddt can not only be done through decorators, but also the metaclass we learned in the test development course can also realize the function of ddt.

Guess you like

Origin blog.csdn.net/m0_73409141/article/details/132496031
ddt