大家好~我是小方,欢迎大家关注笋货测试笔记体完记得俾个like呀
回顾
在上篇中,用例相关的边边角角已经开发完毕,但我们还有个重中之重--用例执行还没开发完毕,今天我们把boss剩余的血量干掉吧···
用例执行
在之前的篇章中,有介绍过数据工厂的流程图,核心点就是内部动态导包,执行用例···
我们的用例信息存在着用例的路径、用例方法、用例的示例入参,而且还知道着整个脚本项目的路径,那我们直接直接动态py文件,直接执行py文件中的某个方法好了,这里的话,我们用到这个importlib
库,我们先来一段简单的demo···
譬如我要执行funcase
项目下,case
目录下的demo.py
里面的add
方法,文件目录和add
方法可看下图
import importlib
params = {
"a":1,
"b":2
}
module = importlib.import_module("funcase.case.demo")
data = getattr(module, "add")(**params)
print(data)
这里用到的是绝对导包,再执行导入文件里面的某个方法,demo比较简单,除了绝对导入还有相对导入,大家可自行找找其他资料扩展一下~
接着我们来封装一下执行方法,执行方法比较简单,大家可看看注释~
class RunScript(object):
@staticmethod
def run(path: str, method: str, params: dict, project: str, directory: str):
"""
动态导包执行方法
:param path: 用例路径
:param method: 方法名
:param params: 入参
:param project: 项目名
:param directory: 脚本目录名
:return:
"""
project_path, case_path = ProjectPath.get(project, directory)
# tag为对应业务线的目录名,py_file为脚本的py文件
tag, py_file = path.split('/')
# 拼接case目录路径
tag_path = os.path.join(case_path, tag)
if not os.path.isdir(tag_path):
raise BusinessException(f"{tag}目录不存在!!!")
# 导包的搜索目录,以该项目为基础
sys.path.append(project_path)
try:
# 绝对导包进去
# from project.directory.tag.py_file import *
module_ = importlib.import_module(f"{project}.{directory}.{tag}.{py_file}")
except ModuleNotFoundError as e:
raise BusinessException(f"导包失败: {e}")
# 校验module是否有对应方法
if not hasattr(module_, method):
raise BusinessException(f"{py_file}.py不存在{method}函数方法!!!")
try:
# 执行module_下的某个方法
script_data = getattr(module_, method)(**params)
return script_data
except:
import traceback
err_msg = traceback.format_exc()
raise BusinessException(f"执行失败: {err_msg}")
finally:
# 移除导包搜索目录
sys.path.remove(project_path)
这里的话,我算是写死整个脚本的存放目录和按照脚本存放目录、业务目录依次绝对导包···
咱们来测试一下吧
data = {
"path": "shop/demo",
"method": "add",
"params": {
"a": 1,
"b": 77776
},
"project": "funcase",
"directory": "case"
}
data = RunScript.run(**data)
print(data)
运行后输出
接着我们将这个运行方法封装成一个api接口,先定义接口的入参
class RunBody(BaseBody):
cases_id : int = Field(..., title="造数场景id", description="必传")
project_id: int = Field(..., title="项目id", description="必传")
method : str = Field(..., title="场景方法名", description="必传")
params : dict=Field(..., title="脚本参数", description="必传")
path: str = Field(..., title="脚本路径", description="必传")
project : str = Field(..., title="项目名", description="必传")
directory : str = Field(..., title="脚本所在目录", description="必传")
requests_id : str = Field(..., title="32位请求id", description="必传")
@validator('path', 'cases_id', 'project_id', 'method', 'requests_id', 'directory')
def name_not_empty(cls, v):
return ToolsSchemas.not_empty(v)
project_id
用来判断项目权限,cases_id
用来后续记录运行日志,requests_id
记录每次运行的请求id,便于查询日志,其他参数跟RunScript.run
入参一致
接着在cases_logic.py
包装RunScript.run
,直接传入参数调用run
方法即可,接着判断脚本的返参是否异常,具体判断异常条件,还是得根据公司实际情况来,执行状态有成功、异常、失败,执行成功和异常都会有实际入参、实际出参等返回
接着继续调试,新建一个plat_run_logic
方法,意为平台执行造数场景,接着在路由函数绑定这个logic方法,最后新建一个路由绑定路由函数
重启后,执行测试
截止目前,数据工厂的脚本运行已经完成了,但是后续会有外链调用、rpc调用~~(讲完日志记录再讲)~~,要是报错了,怎么找出详细的信息呢?emmmm 所以每次运行脚本,我们都得记录运行结果、实际入参和实际出参,运行人等信息,最好能把脚本运行过程的所有日志都记录下来
日志记录
首先,我们先来设计日志表
from sqlalchemy import Column, String, Text, INT, SMALLINT
from app.models.base import FunBaseModel
class DataFactoryRunLog(FunBaseModel):
"""脚本表"""
__tablename__ = 'data_factory_run_log'
cases_id = Column(INT, nullable=False, comment="造数场景id")
requests_id = Column(String(64), nullable=False, comment="请求id")
project_id = Column(INT, nullable=False, comment="项目id")
run_param_in = Column(Text, nullable=False, comment="实际入参")
run_param_out = Column(Text, nullable=True, comment="实际出参")
call_type = Column(SMALLINT, default=0, nullable=False, comment="调用方式, 0: 平台调用 1: 外链调用")
run_status = Column(SMALLINT, default=0, nullable=False, comment="运行状态, 0: 运行成功 1: 运行异常 2: 运行失败")
run_log = Column(Text, nullable=False, comment="运行日志")
def __init__(self, cases_id, requests_id, project_id, run_param_in, run_param_out, run_status, call_type, run_log, user, del_flag=0, id=None):
super().__init__(create_id=user['id'], create_name=user['username'], del_flag=del_flag, id=id)
self.cases_id = cases_id
self.requests_id = requests_id
self.project_id = project_id
self.run_param_in = run_param_in
self.run_param_out = run_param_out
self.run_status = run_status
self.call_type = call_type
self.run_log = run_log
日志表主要记录运行场景的实际入参、实际出参、调用方式、运行状态和运行日志,最后还有请求id···
- 新增逻辑编写
from app.models.run_log import DataFactoryRunLog
from loguru import logger
from app.crud import BaseCrud
from typing import Union
class LogDao(BaseCrud):
log = logger
model = DataFactoryRunLog
@classmethod
def add(cls, cases_id: int, requests_id: str, project_id: int, run_param_in: str,
run_param_out: Union[str, None], run_status: int, call_type: int, run_log: str, user: dict):
"""新增日志"""
log = DataFactoryRunLog(cases_id, requests_id, project_id, run_param_in,
run_param_out, run_status, call_type, run_log, user)
cls.insert_by_model(model_obj = log)
run_logic
引入add方法
运行一下造数场景
调用失败,已新增成功
- 日志展示
前端页面长这样的,支持各种条件进行查询
对应的crud逻辑
emmmm 真的是又臭又长···无法直视
对应的逻辑函数
api对应的路由函数
其他调用方式
- 外链调用
一般这种常用于固定参数传值执行,譬如创建待支付订单,创建时我们可以进行写死参数,维护好参数后,通过一个外链id直接执行脚本,比如链接是这样的==>http://xx.xx.x/api/cases/out?id=727cfd8c379e4518a046911d090590d3
直接在浏览器上运行即可
对应数据工厂这里的功能,看下图
执行逻辑其实比较简单,通过外链id查询params
表维护的参数,再通过cases_id在cases
表查询出cases信息,再进行组装参数,把所有参数都塞进run_logic
方法中···
- rpc调用
这种调用方式比较常用的,算是比较灵活,因为平台上的json编辑器其实没本地postman的json编辑器好用,直接复制这个脚本的rpc链接,在postman里自定义传参即可
通过url获取方法名,再通过方法名查询出cases信息,再进行组装参数,把所有参数都塞进run_logic
方法中···
三种调用方式,其实逻辑都公用了run_logic
方法,只是组装参数的方法不一样···
对了,外链调用和rpc调用这里不用进行权限认证,记得把白名单的url信息维护上
总结
用例运行相关的逻辑已经开发完毕了,难度中等吧,动态导包即为其核心,下篇我们来进行数据报表、git webhook触发同步等功能开发···未完待续···
- 项目地址