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

news/2024/11/24 9:44:35/

高效自动化测试框架-优秀实践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 = Noneself.out_data = None
​def set_data(self, json_dict):self.data = json_dictself.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:returnfor key, value in key_value_iter:current_path = road_step.copy()current_path.append(key)if key == target:yield current_pathif 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 pathreturn []
​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_datafor 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_dictfor 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:@classmethoddef json(self, func):@functools.wraps(func)def wrapper(*args, **kwargs):"""我是 wrapper 的注释"""# 提取fetch入参fetch = Noneif "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 = resreq_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_urlself.token = Noneself.role = role
​def _get_token(self, role=None):if role != "admin" and role != "client":raise ValueErrorurl = {"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.tokenreturn 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


http://www.ppmy.cn/news/37369.html

相关文章

机器学习-模型评估与选择

数据采集和预处理 数据采集和预处理是机器学习中非常重要的一步,因为它们决定了模型能否从数据中学到有效的模式和规律。以下是数据采集和预处理的主要任务: 1、数据采集 数据采集是指从各种来源(如数据库、传感器、网站等)收集…

软件设计模式

软件设计模式 1.设计模式分类图 2.常见的设计模式 2.1代理模式 1.代理模式? 结构型的设计模式。也算是行为型的。核心:调用方和被调用方之间增加一个中介者。也就是代理。调用方->代理->被调用方案例:买房子找中介,求职找猎…

4年资深测试总结,Jmeter 接口测试对请求字段的加密实战,即学即用......

目录:导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜)前言 大家在工作中做接口…

贪心算法(四)

4.更多练习题 4)力扣https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/这道题运用贪心算法,就是每天只考虑与前一天的差价,只要差价大于零,从局部最优来考虑,就应该卖出前一天的股票。这样可以得到全…

[JAVA]重写

1.重写的概念 重写,也被称为覆盖。重写是子类对父类的非静态,非private修饰,非final修饰,非构造的方法实现过程的重新编写。子类重写的方法的参数和返回值类型与父类的方法相同。 2.方法重写的规则 子类重写的方法与父类的参数…

让PyTorch训练速度更快,你需要掌握这17种方法

掌握这 17 种方法,用最省力的方式,加速你的 Pytorch 深度学习训练。近日,Reddit 上一个帖子热度爆表。主题内容是关于怎样加速 PyTorch 训练。原文作者是来自苏黎世联邦理工学院的计算机科学硕士生 LORENZ KUHN,文章向我们介绍了在…

python外篇(内存泄露)

目录 了解 循环引用造成的内存泄露 大量创建对象造成的内存泄漏 全局对象造成的内存泄露 不适当缓存造成的内存泄露 内存分析工具 了解 ### 以下为Python中可能会出现内存泄露的情况: (1) 循环引用:当两个或多个对象相互引用,造成…

JVM 堆

堆的核心概述 堆与进程 1 堆针对一个JVM进程来说是唯一的,一个进程只有一个JVM实例,一个JVM实例中就有一个运行时数据区,一个运行时数据区只有一个推和一个方法区。 2进程包含多个进程,他们是共享一个堆空间的。 3Java堆在JVM启动…