高效自动化测试框架-优秀实践02-接口

高效自动化测试框架-优秀实践02-接口

高效实践点

  1. 编写接口的操作的时候只需要编写接口的url,请求方法,请求体的样例

  2. 其他的将接口封装成服务或者关键字的操作,全部使用装饰器来封装,能做到高效的解耦

  3. 在表示层编写业务测试用例的时候,可以使用函数式的编程方式,非常易读,还非常易于copy,提升编写效率

问题背景

  1. 业务测试用例编写完成之后,需要很多时间去调试,常常是针对入参和返回值做处理

  2. 业务测试用例很不整齐,即代码没有对齐,也不能直观看出脚本的行为,需要仔细读取函数的名称,并进一步理解

  3. 业务脚本的函数命名不规范,不统一,导致不易于理解和修改

  4. 关键字中常常由很多重复的代码,比如接收入参并填充进入请求体中,对于返回值有常常需要根据json格式去取出响应数据

  5. 最开始的框架,提供了很多功能,很灵活,但是多就是少,功能越多越灵活就会导致学习成本和维护成本直线上升,还不如直接统一格式

  6. 关键字中的逻辑是多种多样的,如果需要维护,有可能需要对很多个关键字去维护,耗费时间巨大

解决思路

  1. Api接口的封装,分成4个操作方式去封装,即增删查改

  2. 每个操作行为封装一个接口函数,并且接口中只包含URL,请求体,请求方法

  3. 对于接口的行为,全部由统一的装饰器去封装,分别针对请求体为json,urlencode,xml,html等格式去封装,默认是json

  4. 对于接口行为的封装,将入参装载进入请求体的方法统一封装,即自动寻找对应的数据,然后填充到请求体中去

  5. 对于接口行为的封装,需要提取返回值的时候,上层传入目标值名称,和对应的正则表达式(jsonpath),然后获得对应的数据

相关代码

接口代码示例

from core.logic import Api
​
​
@Api.json
def add_goods(goodsSn="", name="", **kwargs):
    req_method = "POST"
    url = "admin/goods/create"
    body_data = {
        "goods": {
            "picUrl": "",
            "gallery": [],
            "isHot": False,
            "isNew": True,
            "isOnSale": True,
            "goodsSn": "9001",
            "name": None
        },
        "specifications": [{
            "specification": "规格",
            "value": "标准",
            "picUrl": ""
        }],
        "products": [{
            "id": 0,
            "specifications": ["标准"],
            "price": "66",
            "number": "66",
            "url": ""
        }],
        "attributes": []
    }
    return req_method, url, body_data
​
​
@Api.json
def rmv_goods(id="", **kwargs):
    req_method = "POST"
    url = "admin/goods/delete"
    body_data = {"id": None}
    return req_method, url, body_data
​
​
def lst_goods(name="", **kwargs):
    req_method = "GET"
    url = "admin/goods/list"
    body_data = {
        "name": "",
        "order": "desc",
        "sort": "add_time"
    }
    return req_method, url, body_data
​
def dtl_goods(id="", **kwargs):
    req_method = "GET"
    url = "admin/goods/detail"
    body_data = {"id": None}
    return req_method, url, body_data
​

用于封装接口函数,并提供入参填充,返回值提取,和日志打印功能的的装饰器

E:\Develop\LoranTest\core\logic.py

import jsonpath
import functools
import json
from core.base_api import BaseApi
from core.logger.logger_interface import logger
​
​
class RequestData:
    class KeyError(Exception):
        def __init__(self, error_key):
            error_dict = {
                "find_too_many_key": "The key value is incorrect. The request data contains at least two keys named $key. "
                                     "Please modify the incoming key name, that is, $parent key + $key",
                "can_not_find_key": "The key you entered could not be found in the dictionary",
            }
            self.error_info = error_dict[error_key]
​
        def __str__(self):
            return repr(self.error_info)
​
    class FindKeyError(Exception):
        def __str__(self):
            return repr("The key you entered could not be found in the dictionary")
​
    def __init__(self):
        self.data = None
        self.out_data = None
​
    def set_data(self, json_dict):
        self.data = json_dict
        self.out_data = self.data
​
    def iter_node(self, rows, road_step, target):
        if isinstance(rows, dict):
            key_value_iter = (x for x in rows.items())
        elif isinstance(rows, list):
            key_value_iter = (x for x in enumerate(rows))
        else:
            return
        for key, value in key_value_iter:
            current_path = road_step.copy()
            current_path.append(key)
            if key == target:
                yield current_path
            if isinstance(value, (dict, list)):
                yield from self.iter_node(value, current_path, target)
​
    def find_one(self, key: str) -> list:
        path_iter = self.iter_node(self.data, [], key)
        for path in path_iter:
            return path
        return []
​
    def find_all(self, key: str) -> list:
        path_iter = self.iter_node(self.data, [], key)
        return list(path_iter)
​
    def _edit_one_path(self, paths: list, value):
        alias_of_data = self.out_data
        for path in paths[0:-1]:
            alias_of_data = alias_of_data[path]
        alias_of_data[paths[-1]] = value
​
    def change(self, key: str, value):
        if "_" not in key:
            res = self.find_all(key)
            if len(res) > 1:
                raise self.KeyError("find_too_many_key")
            paths = res[0]
            self._edit_one_path(paths, value)
        else:
            key_list = key.split("_")
            res = self.find_all(key_list[-1])
            for temp in key_list:
                if temp not in res[0]:
                    raise self.KeyError("can_not_find_key")
            paths = res[0]
            self._edit_one_path(paths, value)
            pass
​
        return self.out_data
​
    def modify(self, json_dict, **kwargs):
        out_data = json_dict
        for key, value in kwargs.items():
            self.set_data(out_data)
            out_data = self.change(key, value)
        return out_data
​
​
class ResponeseData:
​
    def fetch_one_value(self, data, var_info):
        var_name = var_info[0]
        json_path_reg = var_info[1]
        value = jsonpath.jsonpath(data, json_path_reg)[0]
        return value
​
    def fetch_all_value(self, data, fetch_info):
        # TODO 这里有可能存在一个问题,只对单个调教的信息提取做处理,未对多条件的进行处理
        for info in fetch_info:
            return self.fetch_one_value(data, info)
​
​
class Api:
    @classmethod
    def json(self, func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            """我是 wrapper 的注释"""
            # 提取fetch入参
            fetch = None
            if "fetch" in kwargs.keys():
                fetch = kwargs["fetch"]
                del kwargs['fetch']
​
            res = func(*args, **kwargs)
            logger.debug(func.__name__ + "::kwargs: " + json.dumps(kwargs))
​
            req_method, url, body_data = res
            req_body = RequestData().modify(body_data, **kwargs)
            logger.debug(func.__name__ + "::req_body: " + json.dumps(req_body))
​
            req_api = BaseApi(role="admin")
            rsp_data = req_api.send(method=req_method, url=url, json=req_body)
            logger.debug(func.__name__ + "::req_body: " + json.dumps(rsp_data))
​
            # 针对fetch入参做处理
            if fetch:
                fetch_var = ResponeseData().fetch_all_value(rsp_data, fetch)
                logger.debug(func.__name__ + "::req_body: " + json.dumps(fetch_var))
        return wrapper
​
    def form(self):
        pass
​
    def urlencoded(self):
        pass
​
    def binary(self):
        pass
​
    def test(self):
        pass
​
    def js(self):
        pass
​
    def html(self):
        pass
​
    def xml(self):
        pass
​
​
​

接口的请求的基类

E:\Develop\LoranTest\core\base_api.py

import json
import requests
from core.logger.logger_interface import logger
from config.environment import Environment
​
​
class BaseApi:
    def __init__(self, role=None):
        env = Environment()
        self.base_url = env.base_url
        self.token = None
        self.role = role
​
    def _get_token(self, role=None):
        if role != "admin" and role != "client":
            raise ValueError
        url = {
            "admin": "admin/auth/login",
            "client": "wx/auth/login",
        }
        data = {
            "admin": {"username": "admin123", "password": "admin123"},
            "client": {"username": "user123", "password": "user123"},
        }
        req_token = {
            "admin": "X-Litemall-Admin-Token",
            "client": "X-Litemall-Token",
        }
        req = requests.request("post", self.base_url + url[role], json=data[role])
        self.token = {req_token[role]: req.json()["data"]["token"]}
        pass
​
    def _set_token(self, request_infos):
        if self.token is None:
            self._get_token(role=self.role)
​
        if request_infos.get("headers"):
            request_infos["headers"].update(self.token)
        else:
            request_infos["headers"] = self.token
        return request_infos
​
    def send(self, method="", url="", **kwargs):
        kwargs = self._set_token(kwargs)
        rsp = requests.request(method, self.base_url + url, **kwargs)
        rsp_json = rsp.json()
        logger.debug(f"BaseApi::send ==> {url}接口的响应为{json.dumps(rsp_json, indent=2, ensure_ascii=False)}")
        return rsp_json
​
​

待改进的地方

  1. 返回值目前仅支持返回一条数据

  2. 请求的积累中,需要重复去获取token信息,速度慢,以后可以改成直接读取redis的方式去实现

  3. 查询某类资源的时候,需要用变量去接收返回值,代码格式还不够统一,可以考虑使用传入类变量的方式去实现,直接接收返回值的方式,并且Python在作用于这块限制的比较严格

项目地址

GitHub - WaterLoran/LoranTest

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值