数据工厂系列(28)用例运行-最终篇

210 阅读6分钟

大家好~我是小方,欢迎大家关注笋货测试笔记体完记得俾个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触发同步等功能开发···未完待续···