开源测试工具 基于 Sonic 的云测平台新增脚本任务模式的设计与实现

imath60 for Sonic云真机系列 · 2022年07月26日 · 最后由 陈恒捷 回复于 2022年07月26日 · 6682 次阅读

前言:

用户社区 https://sonic-cloud.wiki/
官方网站 https://sonic-cloud.gitee.io/

一、 方案概述

云测平台新增脚本模式,可在平台上直接上传脚本;
云测平台新增任务模式,任务可关联脚本、设备、agent 等信息;任务支持立即执行、修改、定期执行、查看报告等基本功能;
白屏监控可作为其中的一项任务执行,关联至白屏监控脚本上;

二、 sonic 脚本管理

2.1 前端 ui 设计

导航栏新增一列脚本管理,页面展示:
操作行:新增脚本
脚本列表,列表后展示操作按钮,包括:删除、调试、下载
权限管理:初期全放开,后期接入权限模块后再做详细拆分

参考:

2.2 新增脚本

点击按钮,可以选择文件/文件夹(或者压缩包)上传,此处可直接使用现有实现逻辑(压测平台参数化接口),将对应文件上传至 wos 存储
存储成功后,将该脚本相关信息存储至脚本表中

表设计如下:

脚本 id 脚本名称 关联任务 id 资源 url 创建时间 更新人员 创建人员 更新时间 脚本类型 md5 校验 文件大小 删除位标记 脚本状态 扩展字段
id script_name task_id res_url create_time update_uid create_uid update_time script_type md5_value res_size flag_deleted script_status export

2.2.1 前端交互设计

参考:https://blog.csdn.net/weixin_46038888/article/details/124117369

// 新增上传操作
// HTML 
<el-upload
            ref="upload"
            class="upload-demo"
            action="#"  
            accept=".xlsx, .xls"
            :auto-upload="false"
            :on-change="uploadFile"
            :show-file-list="false"
          >
          <el-button type="warning">导入商品</el-button>
        </el-upload>
// JS
uploadFile (item) {
        let formData = new FormData()
        let file = item.raw
        formData.append('file', file)
        this.$http({
          url: ' ', //后端提供的接口
          method: 'post',
          data: formData,
          headers: {
            'Content-Type': 'multipart/form-data'
          }
        }).then(({data}) => {
          this.$alert(data.data)
        })
    }

2.2.2 后端逻辑

新增接口,包装墨洁的上传接口
当文件上传成功后,
将新增上传文件相关信息落库
参考:

创建两个脚本类型: DTO+VO
script.java

package org.cloud.sonic.common.models.domain;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.gitee.sunchenbin.mybatis.actable.annotation.*;
import com.gitee.sunchenbin.mybatis.actable.constants.MySqlCharsetConstant;
import com.gitee.sunchenbin.mybatis.actable.constants.MySqlEngineConstant;
import org.cloud.sonic.common.models.base.TypeConverter;
import org.cloud.sonic.common.models.dto.TestSuitesDTO;
import io.swagger.annotations.ApiModel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.io.Serializable;

@ApiModel(value = "脚本对象", description = "")
@Data
@Accessors(chain = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("script")
@TableComment("脚本对象表")
@TableCharset(MySqlCharsetConstant.DEFAULT)
@TableEngine(MySqlEngineConstant.InnoDB)
public class Script implements Serializable, TypeConverter<Script, ScriptDTO> {

    @TableId(value = "id", type = IdType.AUTO)
    @IsAutoIncrement
    private Integer id;

    @TableField
    @TableLogic
    @Column(isNull = false, comment = "脚本状态")
    private boolean deleted;

    @TableField
    @Column(value = "resource_url", isNull = false, comment = "脚本资源url")
    private String resourceUrl;

    @TableField
    @Column(value = "create_time", isNull = false, comment = "创建时间")
    private String createTime;

    @TableField
    @Column(value = "update_time", comment = "更新时间")
    private String updateTime;

    @TableField
    @Column(value = "create_person", isNull = false, comment = "创建人员")
    private String createPerson;

    @TableField
    @Column(value = "update_person", comment = "更新人员")
    private String updatePerson;

    @TableField
    @Column(isNull = false, comment = "脚本类型")
    private String type;

    @TableField
    @Column(isNull = false, comment = "脚本大小")
    private Integer size;

    @TableField
    @Column(value = "renwu_id", isNull = false, comment = "脚本关联任务ID")
    @Index(value = "IDX_PROJECT_ID", columns = {"id"})
    private Integer renwuId;
}

scriptDTO.java

package org.cloud.sonic.common.models.dto;

import org.cloud.sonic.common.models.base.TypeConverter;
import org.cloud.sonic.common.models.domain.TestSuites;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Positive;
import java.io.Serializable;
import java.util.List;

@ApiModel("测试套件DTO 模型")
@Data
@Accessors(chain = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TestSuitesDTO implements Serializable, TypeConverter<TestSuitesDTO, TestSuites> {

    @ApiModelProperty(value = "id", example = "1")
    Integer id;

    @NotBlank
    @ApiModelProperty(value = "测试套件名称", required = true, example = "首页测试套件")
    String name;

    @Positive
    @ApiModelProperty(value = "测试套件平台类型", required = true, example = "1")
    Integer platform;

    @Positive
    @ApiModelProperty(value = "覆盖类型", required = true, example = "1")
    Integer cover;

    @Positive
    @ApiModelProperty(value = "项目id", required = true, example = "1")
    Integer projectId;

    @ApiModelProperty(value = "包含的测试用例")
    List<TestCasesDTO> testCases;

    @ApiModelProperty(value = "指定设备列表")
    List<DevicesDTO> devices;
}

添加 mappler 文件

package org.cloud.sonic.controller.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.cloud.sonic.common.models.domain.TestSuites;
import org.apache.ibatis.annotations.Mapper;

/**
 *  Mapper 接口
 * @author JayWenStar
 * @since 2021-12-17
 */
@Mapper
public interface TestSuitesMapper extends BaseMapper<TestSuites> {

}

2.3 删除脚本

将该脚本状态置为 DELETE
调用 save 接口

2.4 下载脚本

将该文件资源下载至本地,新增一个下载接口

2.5 脚本调试执行(初步方案,后期优化前端加上执行日志展示框)

2.5.1 整体流程

  1. 发起调试执行接口
  2. 接口调用脚本调试方法
  3. server 端处理一些异常判断,分配执行 agent 与设备,从数据库获取该脚本相关信息,拼接消息类型下发至 agent
  4. agent 端:脚本下载、判断任务类型、环境校验/配置、调用脚本执行
  5. agent 端:等待 Python 脚本回调消息,Python 脚本执行完毕后,给 java 一个回调
  6. agent 端:整体任务执行完毕后,给 server 通信

2.5.2 server 处理流程

2.5.3 agent 处理流程

三、任务管理

实现逻辑

可直接参考 org.cloud.sonic.simple.controller.TestSuitesController.runSuite 方法

public RespModel<String> runSuite(int suiteId, String strike) {
    TestSuitesDTO testSuitesDTO;
    if (existsById(suiteId)) {
        testSuitesDTO = findById(suiteId);
    } else {
        return new RespModel<>(3001, "测试套件已删除!");
    }

    if (testSuitesDTO.getTestCases().size() == 0) {
        return new RespModel<>(3002, "该测试套件内无测试用例!");
    }

    List<Devices> devicesList = BeanTool.transformFromInBatch(testSuitesDTO.getDevices(), Devices.class);
    for (int i = devicesList.size() - 1; i >= 0; i--) {
        if (devicesList.get(i).getStatus().equals(DeviceStatus.OFFLINE) || devicesList.get(i).getStatus().equals(DeviceStatus.DISCONNECTED)) {
            devicesList.remove(devicesList.get(i));
        }
    }
    if (devicesList.size() == 0) {
        return new RespModel<>(3003, "所选设备暂无可用!");
    }

四、整体设计方案流程

##4.1 server 执行任务流程

##4.2 agent 执行任务流程

4.3 脚本执行流程

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 3 条回复 时间 点赞

很详细的技术方案,点个赞。

这个是已经加入到现在最新的 sonic 开源版了吗?

陈恒捷 回复

这个是他们公司内部二开的成果,不过 Sonic 下版本在制作插件化,可以暴露这些能力,包括脚本、图像等等可以用户自己扩展😆

Eason 回复

soga,明白。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册