• 小小装饰器大大用处


    事情是这样,我们正在编写接口自动化用例。因为基本上都是复杂的场景测试。

    例如测试支付业务的过程:

    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~!

  • 相关阅读:
    JAVA线程池1.0
    【Linux基础】zsh 和 Oh My Zsh
    C++之内建函数对象
    求平均查找长度(成功+失败)
    SpringBoot 集成 SpringSecurity + MySQL + JWT 附源码,废话不多直接盘
    Spring如何解决循环依赖问题
    小红书品牌账号怎么运营,如何传播规划?
    22.11.20补卡 javaSE多线程学习笔记
    优先级队列
    Bean Validation(数据校验注解大全 @Valid和@Validated的区别。
  • 原文地址:https://www.cnblogs.com/fnng/p/16260417.html