高效自动化测试框架-优秀实践02-接口
高效实践点
-
编写接口的操作的时候只需要编写接口的url,请求方法,请求体的样例
-
其他的将接口封装成服务或者关键字的操作,全部使用装饰器来封装,能做到高效的解耦
-
在表示层编写业务测试用例的时候,可以使用函数式的编程方式,非常易读,还非常易于copy,提升编写效率
问题背景
-
业务测试用例编写完成之后,需要很多时间去调试,常常是针对入参和返回值做处理
-
业务测试用例很不整齐,即代码没有对齐,也不能直观看出脚本的行为,需要仔细读取函数的名称,并进一步理解
-
业务脚本的函数命名不规范,不统一,导致不易于理解和修改
-
关键字中常常由很多重复的代码,比如接收入参并填充进入请求体中,对于返回值有常常需要根据json格式去取出响应数据
-
最开始的框架,提供了很多功能,很灵活,但是多就是少,功能越多越灵活就会导致学习成本和维护成本直线上升,还不如直接统一格式
-
关键字中的逻辑是多种多样的,如果需要维护,有可能需要对很多个关键字去维护,耗费时间巨大
解决思路
-
Api接口的封装,分成4个操作方式去封装,即增删查改
-
每个操作行为封装一个接口函数,并且接口中只包含URL,请求体,请求方法
-
对于接口的行为,全部由统一的装饰器去封装,分别针对请求体为json,urlencode,xml,html等格式去封装,默认是json
-
对于接口行为的封装,将入参装载进入请求体的方法统一封装,即自动寻找对应的数据,然后填充到请求体中去
-
对于接口行为的封装,需要提取返回值的时候,上层传入目标值名称,和对应的正则表达式(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
待改进的地方
-
返回值目前仅支持返回一条数据
-
请求的积累中,需要重复去获取token信息,速度慢,以后可以改成直接读取redis的方式去实现
-
查询某类资源的时候,需要用变量去接收返回值,代码格式还不够统一,可以考虑使用传入类变量的方式去实现,直接接收返回值的方式,并且Python在作用于这块限制的比较严格文章来源:https://www.toymoban.com/news/detail-405154.html
项目地址
GitHub - WaterLoran/LoranTest文章来源地址https://www.toymoban.com/news/detail-405154.html
到了这里,关于高效自动化测试框架-优秀实践02-接口的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!