Little ornaments are of great use

The thing is, we are writing interface automation use cases. Because they are basically complex scenario tests.

For example, the process of testing payment business:

  1. User login
  2. Join shopping
  3. place an order
  4. payment

In other words, if you want to test the payment business, you must probably call the first three interfaces. Then we need to encapsulate the first three interfaces. Take user login as an example.

import json
import requests


class UserLogin:

    def __init__(self, username, password):
        self.username = username
        self.password = password

    def get_token(self):
        """Get user login token"""
        url = "http://httpbin.org/post"

        data = {
            "username": self.username,
            "password": self.password,
            "token": "token123"  # Pretend this is the toKen returned by the interface
        }
        r = requests.post(url, data=data)

        if r.status_code != 200:
            raise ValueError("Interface request failed")
        
        try:
            r.json()
        except json.decoder.JSONDecodeError:
            raise ValueError("Interface is not json format")

        if r.json()["headers"]["Host"] != "httpbin.org":
            raise ValueError("Interface returned required parameter error")
        
        token = r.json()["form"]["token"]
        return token


if __name__ == '__main__':
    user_login = UserLogin("zhangsan", "mima123")
    token = user_login.get_token()
    print(token)

Just look at the encapsulation of the interface, there seems to be no problem ~! However, the following process is required after each interface call:

  1. Judge whether the status code is 200. If it is not 200, the interface is blocked.
  2. Only then judge whether the return value format is JSON. If not, you cannot extract data.
  3. Check the necessary parameters returned by the interface, for example: r.json()["headers"]["Host"].
  4. Extract the data returned by the interface. For example: r.json()["form"]["token"].

python decorator

Decorators are an important part of Python. Simply put: they are functions that modify the functionality of other functions. It helps to make our code shorter and more python.

Instead of leading you step by step to deduce how to create a decorator, let's take a look at the examples.

  • Decorator
def dec():
    """
    python Decorator
    """
    def decorator(func):
        
        def wrapper(*args, **kwargs):
            func_name = func.__name__
            print(f"Decorated method name: {func_name}")
            print(f"Method input parameters args: {args}")
            print(f"Method input parameters kwargs: {kwargs}")

            r = func(*args, **kwargs)
            print(f"Return value of method return: {r}")

        return wrapper

    return decorator

The frame of the decorator looks like this, focusing on the input and return values of the decorator.

  • usage
@dec()
def add(a, b):
    c = a + b
    return c


add(1, 2)

Call the @dec() decorator to decorate an add() function

Running results

Decorated method name: add
 Method input parameters args: (1, 2)
Method input parameters kwargs: {}
Return value of method return: 3

This decorator can get the name, input parameter and return value of the decorated function. Is it very interesting.

Interface inspection decorator

  • check_response() decorator implementation
import json
from jmespath import search


def check_response(
        describe: str = "",
        status_code: int = 200,
        ret: str = None,
        check: dict = None,
        debug: bool = False):
    """
    checkout response data
    :param describe: interface describe
    :param status_code: http status code
    :param ret: return data
    :param check: check data
    :param debug: debug Ture/False
    :return:
    """
    def decorator(func):
        def wrapper(*args, **kwargs):
            func_name = func.__name__
            if debug is True:
                print(f"Execute {func_name} - args: {args}")
                print(f"Execute {func_name} - kwargs: {kwargs}")

            r = func(*args, **kwargs)
            flat = True
            if r.status_code != status_code:
                print(f"Execute {func_name} - {describe} failed: {r.status_code}")
                flat = False

            try:
                r.json()
            except json.decoder.JSONDecodeError:
                print(f"Execute {func_name} - {describe} failed: Not in JSON format")
                flat = False

            if debug is True:
                print(f"Execute {func_name} - response:\n {r.json()}")

            if flat is True:
                print(f"Execute {func_name} - {describe} success!")

            if check is not None:
                for expr, value in check.items():
                    data = search(expr, r.json())
                    if data != value:
                        print(f"Execute {func_name} - check data failed: {value}")
                        raise ValueError(f"{data} != {value}")

            if ret is not None:
                data = search(ret, r.json())
                if data is None:
                    print(f"Execute {func_name} - return {ret} is None")
                return data
            else:
                return r.json()

        return wrapper

    return decorator
  1. The core is to expand on the shelf of the front @dec() decorator and add parameters and return value verification.
  2. The code refers to the jmespath library, mainly to extract data.
  • use
import requests

class UserLogin:

    def __init__(self, username, password):
        self.username = username
        self.password = password

    @check_response("Get user login token", 200, ret="form.token", check={"headers.Host": "httpbin.org"}, debug=True)
    def get_token(self):
        """Get user login token"""
        url = "http://httpbin.org/post"

        data = {
            "username": self.username,
            "password": self.password,
            "token": "token123"  # Pretend to be the toKen returned by the interface
        }
        r = requests.post(url, data=data)
        return r


if __name__ == '__main__':
    user_login = UserLogin("zhangsan", "mima123")
    token = user_login.get_token()
    print(token)

Via @check_response() decorates the called interface, which can greatly simplify the code. Parameter Description:

  • Get the user login token: interface description.

  • 200: check whether the status code of the interface return value is 200.

  • ret="form.token": extract the token in the returned value of the interface through jmespath.

  • check={"headers.Host": "httpbin.org"}: check the parameters contained in the interface return value. It is equivalent to asserting interface data.

  • debug=True: enable debug to print detailed information for debugging.

Operation information

Execute get_token - args: (<__main__.UserLogin object at 0x000001EF4397E1C0>,)
Execute get_token - kwargs: {}
Execute get_token - response:
 {'args': {}, 'data': '', 'files': {}, 'form': {'password': 'mima123', 'token': 'token123', 'username': 'zhangsan'}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Content-Length': '49', 'Content-Type': 'application/x-www-form-urlencoded', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.25.0', 'X-Amzn-Trace-Id': 'Root=1-62682337-2cd21bd0599368e54d2063bd'}, 'json': None, 'origin': '173.248.248.88', 'url': 'http://httpbin.org/post'}
Execute get_token - Get user login token success!
token123

With this little decorator, we have reduced a lot of the same sample code. Finally, python decorator YYDS~!

Tags: Python

Posted by wilku on Wed, 01 Jun 2022 23:10:08 +0530