Source code teaching: small editor teaches you 30 lines of code to realize ddt module

preface

Most of the partners who have done automation with python should have used ddt module. It is undeniable that ddt module is really very useful. It can automatically generate test cases according to use case data, which can easily separate test data from test case execution logic. Next, let's take everyone together and roll out a ddt.

1. Implementation principle of DDT

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

728 x 131 752 x 135

ddt is very concise in use, that is, two decorators, @ ddt decorator decorator test class, @ data decorator decorator use case method, and pass in test data. The effect of these two decorators is to automatically generate use cases according to the incoming use case data. How is it realized? In fact, the idea of implementation is also very simple, just two steps:

Step 1: save the transferred use case data

Step 2: traverse the use case data. Each time you traverse a piece of data, you will dynamically add a use case method to the test class.

The two decorators in ddt actually implement the following two steps:

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

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

2. Implementation of data decorator

Earlier, we talked about the data decorator. What we do is to save the use case data. So how to save it? In fact, the simplest way is to save the properties of the decorated use case method. Next, let's implement:

Let's take a look at a case of ddt

 
@ddt
class TestLogin(unittest.TestCase):

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

Little friends who have known the principle of decorators should know that the execution effect of the above line of @ data(11,22) code is the same

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 and 22 as parameters, then return a callable object (function), call the returned function again and pass in the use case method. If the calling process is clear, we can define the decorator function data 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 test is executed_ login = data(11,22)(test_login)

First call 11 and 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 to pass the decorated test_. Login method

In the wrapper function, we save the use case data as test_login is the PARAMS attribute of this method, and then test_login return

So far, with the decorator data, we will save the use case data

3. Implementation of ddt decorator

After we save the use case data through the data decorator, we then implement the DDT decorator to generate test cases according to the use case data. When the previous case @ddt decorates the test class, the execution effect is actually the same as the following code

TestLogin = ddt(TestLogin)

This line of code is to pass the decorated class into the decorator function ddt, and then assign the return value to TestLogin. In the previous analysis, we said that what ddt decorator does is to traverse use case data and dynamically add use case methods to test classes. Next, we will implement the internal logic of 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:

Internal logic description of ddt function:

1. When calling ddt, the test class will be passed in as a parameter,

2. Then through CLS__ dict__ Get all the properties and methods of the test and traverse them

3. Judge whether the attribute or method of the variable has the attribute PARAMS,

4. If yes, it indicates that this method has been decorated with the data decorator and passed in the use case data.

5. Get all use case data through getattr(func, "PARAMS") for traversal.

6. Each time a set of use case data is traversed, a use case method name is produced, and then a use case method is dynamically added to the test class.

7. After traversing all the case data, delete the test method originally defined by the test class

8. Finally, return the test class

So far, when the basic functions of ddt and data decorator functions are realized, test cases can be automatically generated according to the use case data. Next, we write a test class to check

# Define decorator function data
def data(*args):
    def wrapper(func):
        setattr(func, "PARAMS", args)
        return func

    return wrapper

# Define decorator function 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

# Writing test classes
@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 according to use case data has been realized.

4. Solve the problem of case parameter transfer

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 data transfer of use cases? We can modify the use case method through a closure function, so as to transfer the use case test as a parameter when calling the use case method. The function code of modifying the original use case method is 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 an update_ test_ Closure function of func

The closure function receives two parameters: test_ Func (receive use case method), case_ Data (receive case data)

The closure function returns a nested function, which calls the original use case method and passes in test data

When defining the nested function, the decorator wraps in the functools module is used to decorate it. It can make the wrapper nested function have test_func is the relevant attribute of this use case function.

Now let's go back to the ddt function previously written, and call update_ before adding test cases to the test class. test_ Func method modifies 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")):
                # Generate a use case method name
                new_test_name = "{}_{}".format(name, index)
                # Modify the original test method and set the case data as the parameters of the test method
                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, all the test methods we dynamically add to the test class in the test class actually point to update_ test_ The wrapper function defined in func is actually an executed wrapper function when executing tests. Inside the wrapper 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.

The following is a complete case. You can copy the past and run it, write it yourself, and expand it according to your own needs. If there is something you don't understand after reading the article, you can learn about the test and development course of our lemon class. After learning the decorator in the test and development course, the class will teach you how to realize ddt. Of course, the implementation of ddt can be done not only through the decorator, but also through the metaclasses we learn in the test development course.

Complete case

from functools import wraps
import unittest

# --------Implementation of 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")):
                # Generate a use case method name
                new_test_name = "{}_{}".format(name, index)
                # Modify the original test method and set the case data as the parameters of the test method
                test_func = update_test_func(func, case_data)
                setattr(cls, new_test_name, test_func)
            else:
                delattr(cls, name)
    return cls

# --------Test case writing--------
@ddt
class TestDome(unittest.TestCase):
    @data(11, 22, 33, 44)
    def test_demo(self, data):
        assert data < 40
#---------Use case execution-----------
unittest.main()

If you like to help you, remember to pay more attention and don't get lost

There is a way to get the personal home page. You can get some software test tools, interview questions and data packages collected by individuals. They are free and need private letter Xiaobian "data, which may be helpful to you!

Click the link to receive

[complete set of basic / advanced / transition data for software testing]

Thank you for your great reading 🥰

 

Keywords: Python API testing

Added by jamesp on Tue, 04 Jan 2022 01:47:13 +0200