小小裝飾器大大用處

蟲師發表於2022-05-11

事情是這樣,我們正在編寫介面自動化用例。因為基本上都是複雜的場景測試。

例如測試支付業務的過程:

  1. 使用者登入
  2. 加入購物
  3. 下單
  4. 支付

也就是說,如你想測試支付業務,大概必須要呼叫前面三個介面。那我們就需要把前面三個介面進行封裝。以使用者登入為例。

import json
import requests


class UserLogin:

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

    def get_token(self):
        """獲取使用者登入token"""
        url = "http://httpbin.org/post"

        data = {
            "username": self.username,
            "password": self.password,
            "token": "token123"  # 假裝這是介面返回的toKen
        }
        r = requests.post(url, data=data)

        if r.status_code != 200:
            raise ValueError("介面請求失敗")
        
        try:
            r.json()
        except json.decoder.JSONDecodeError:
            raise ValueError("介面不是json格式")

        if r.json()["headers"]["Host"] != "httpbin.org":
            raise ValueError("介面返回必要引數錯誤")
        
        token = r.json()["form"]["token"]
        return token


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

單看介面這麼封裝,貌似沒有問題~!但每個介面呼叫之後都需要經歷以下過程:

  1. 判斷狀態碼是否為 200,如果不是 200 說明介面不通。
  2. 僅接著判斷返回值格式是否為 JSON,如果不是,你就無法提取資料。
  3. 檢查介面返回的必要引數,例如:r.json()["headers"]["Host"]
  4. 提取介面返回的資料。例如: r.json()["form"]["token"]

python裝飾器

裝飾器(Decorators)是 Python 的一個重要部分。簡單地說:他們是修改其他函式的功能的函式。他有助於讓我們的程式碼更簡短,也更Pythonic。

這裡就不領著大家一步步推演如何建立一個裝飾器,直接看例子。

  • 裝飾器
def dec():
    """
    python裝飾器
    """
    def decorator(func):
        
        def wrapper(*args, **kwargs):
            func_name = func.__name__
            print(f"被裝飾的方法名: {func_name}")
            print(f"方法的入參 args: {args}")
            print(f"方法的入參 kwargs: {kwargs}")

            r = func(*args, **kwargs)
            print(f"方法的返回值 return: {r}")

        return wrapper

    return decorator

裝飾器的架子大概長這個樣子,重點在裝飾器的入參和返回值。

  • 用法

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


add(1, 2)

呼叫@dec()裝飾器來裝飾一個add() 函式

執行結果

被裝飾的方法名: add
方法的入參 args: (1, 2)
方法的入參 kwargs: {}
方法的返回值 return: 3

這個裝飾器可以拿到被裝飾函式的名字入參返回值,是不是很有意思。

介面檢查裝飾器

  • check_response() 裝飾器實現
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. 核心就是在前面@dec() 裝飾器的架子上擴充套件,增加引數和返回值校驗。
  2. 程式碼引用了jmespath 庫,主要是為了提取資料。
  • 使用
import requests

class UserLogin:

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

    @check_response("獲取使用者登入token", 200, ret="form.token", check={"headers.Host": "httpbin.org"}, debug=True)
    def get_token(self):
        """獲取使用者登入token"""
        url = "http://httpbin.org/post"

        data = {
            "username": self.username,
            "password": self.password,
            "token": "token123"  # 假裝是介面返回的toKen
        }
        r = requests.post(url, data=data)
        return r


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

通過@check_response() 裝飾被呼叫介面,可以極大的簡化程式碼。引數說明:

  • 獲取使用者登入token: 介面描述。

  • 200: 檢查介面返回值狀態碼是否為 200

  • ret="form.token": 提取介面返回值中的token,通過jmespath

  • check={"headers.Host": "httpbin.org"}: 檢查介面返回值中包含的引數。相當於對介面資料進行斷言。

  • debug=True: 開啟debug,列印詳細資訊,方便除錯。

執行資訊

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 - 獲取使用者登入token success!
token123

有了這個小小的裝飾器,我們減少了很多相同的樣例程式碼。最後,python裝飾器 YYDS~!

相關文章