【测试平台系列】第一章 手撸压力机(八)- 实现testobject接口

简介: 上一章中我们已经启动了一个/engine/run/testObject/接口,但是我们还没有具体的实现该接口,本章我们就来实现一下该接口。

上一章中我们已经启动了一个/engine/run/testObject/接口,但是我们还没有具体的实现该接口,本章我们就来实现一下该接口。

首先,我们在global目录下新建common/response.go,我们在response.go文件中定义好/engine/run/testObject/接口的响应信息。


// Package common -----------------------------
// @file      : response.go
// @author    : 被测试耽误的大厨
// @contact   : 13383088061@163.com
// @time      : 2023/7/4 18:43
// -------------------------------------------
package common

import "github.com/gin-gonic/gin"

// Response 定义一个响应体,当请求我们的接口时,返回使用
type Response struct {
  Code    int32       `json:"code"`    // 响应码
  Id      string      `json:"id"`      // 唯一id
  Message string      `json:"message"` // 消息
  Data    interface{} `json:"data"`    // 具体信息
}


/*
  返回响应信息,使用ctx.Json返回json数据
*/

func ReturnResponse(ctx *gin.Context, code int32, id string, msg string, data interface{}) {
  ctx.JSON(
    200,
    Response{
      code,
      id,
      msg,
      data,
    })
}

然后,我们在model/test_object.go中定义一个接口体接收我们发送的请求的请求及响应信息。test_object.go全部代码如下:



// Package model -----------------------------
// @file      : test_object.go
// @author    : 被测试耽误的大厨
// @contact   : 13383088061@163.com
// @time      : 2023/6/11 20:38
// -------------------------------------------
package model

// TestObjectResponse 响应信息, 用于返回给调用方
type TestObjectResponse struct {
  Name            string            `json:"name"`         // 对象名称
  Id              string            `json:"id"`           // 唯一id
  ParentId        string            `json:"parent_id"`    // 父id
  ObjectType      string            `json:"object_type"`  // 对象类型http、websocket、dubbo等
  ItemId          string            `json:"item_id"`      // 项目Id
  TeamId          string            `json:"team_id"`      // 团队Id
  SourceId        string            `json:"source_id"`    // 源Id
  ChannelId       string            `json:"channel_id"`   // 渠道Id比如YApi,postman等
  ChannelType     string            `json:"channel_type"` // 渠道类型
  Code            int               `json:"code"`
  RequestHeaders  map[string]string `json:"request_headers"`
  ResponseHeaders map[string]string `json:"response_headers"`
  Response        string            `json:"response"`
  RequestTime     int64             `json:"request_time"`
}

// TestObject 测试对象结构体
type TestObject struct {
  Name        string      `json:"name"`         // 对象名称
  Id          string      `json:"id"`           // 唯一id
  ParentId    string      `json:"parent_id"`    // 父id
  ObjectType  string      `json:"object_type"`  // 对象类型http、websocket、dubbo等
  ItemId      string      `json:"item_id"`      // 项目Id
  TeamId      string      `json:"team_id"`      // 团队Id
  SourceId    string      `json:"source_id"`    // 源Id
  ChannelId   string      `json:"channel_id"`   // 渠道Id比如YApi,postman等
  ChannelType string      `json:"channel_type"` // 渠道类型
  HttpRequest HttpRequest `json:"http_request"`
}

// Dispose 测试对象的处理函数,在go语言中 Dispose方法是TestObject对象的方法,其他对象不能使用
func (to TestObject) Dispose(response *TestObjectResponse) {
  switch to.ObjectType {
  case HTTP1: // 由于我们有个多类型,为了方便统计,我们定义好变量,直接进行比对即可
    to.HttpRequest.Request(response)
    return
  }
  return
}

上述代码中,大家可以我们把Dispose函数进行了优化:

原代码:
// Dispose 测试对象的处理函数,在go语言中 Dispose方法是TestObject对象的方法,其他对象不能使用
func (to TestObject) Dispose() {
  switch to.ObjectType {
  case HTTP1: // 由于我们有多个类型,为了方便统计,我们定义好变量,直接进行比对即可
    client.RequestHttp(to.HttpRequest)
  }
}


现代码:
// Dispose 测试对象的处理函数,在go语言中 Dispose方法是TestObject对象的方法,其他对象不能使用
func (to TestObject) Dispose(response *TestObjectResponse) {
  switch to.ObjectType {
  case HTTP1: // 由于我们有多个类型,为了方便统计,我们定义好变量,直接进行比对即可
    to.HttpRequest.Request(response)
    return
  }
  return
}

在现代码中,我们首先给Dispose函数加了个TestObjectResponse的指针参数,其次,我们将client.RequestHttp函数修改为HttpRequest.Request方法。现在,我们将client目录删除(包括http_client.go文件)。然后我们修改model/http_request.go文件如下:


package model

import (
  "crypto/tls"
  "crypto/x509"
  "fmt"
  "github.com/valyala/fasthttp"
  "io/ioutil"
  "kitchen-engine/global/log"
  "strings"
  "time"
)

// HttpRequest http请求的结构
type HttpRequest struct {
  Url                string             `json:"url"`                  // 接口uri
  Method             string             `json:"method"`               // 接口方法,Get Post Update...
  Headers            []Header           `json:"headers"`              // 接口请求头
  Querys             []Query            `json:"querys"`               // get请求时的url
  Cookies            []Cookie           `json:"cookies"`              // cookie
  Body               string             `json:"body"`                 // 请求提
  HttpClientSettings HttpClientSettings `json:"http_client_settings"` // http客户端配置
}

func (hr *HttpRequest) Request(response *TestObjectResponse) {

  // 使用fasthttp 协程池

  // 新建一个http请求
  req := fasthttp.AcquireRequest()
  defer fasthttp.ReleaseRequest(req)
  // 新建一个http响应接受服务端的返回
  resp := fasthttp.AcquireResponse()
  defer fasthttp.ReleaseResponse(resp)

  // 新建一个http的客户端, newHttpClient是一个方法,在下面
  client := newHttpClient(hr.HttpClientSettings)

  // 添加该请求的http方法:get、post、delete、update等等
  req.Header.SetMethod(hr.Method)

  // 设置header
  for _, header := range hr.Headers {
    if strings.EqualFold(header.Field, "host") {
      // 由于在header中设置host不生效,所以需要强制设置生效
      req.SetHost(header.Value)
      req.UseHostHeader = true
    } else {
      req.Header.Add(header.Field, header.Value)
    }

  }

  // 设置cookie
  for _, cookie := range hr.Cookies {
    req.Header.SetCookie(cookie.Field, cookie.Value)
  }

  // 如果query不为空则设置query
  urlQuery := req.URI().QueryArgs()
  for _, query := range hr.Querys {
    if !strings.Contains(hr.Url, query.Field) {
      queryBy := []byte(query.Value)
      urlQuery.AddBytesV(query.Field, queryBy)
      hr.Url += fmt.Sprintf("&%s=%s", query.Field, query.Value)
    }
  }

  req.SetBody([]byte(hr.Body))
  // 添加该请求的http的url
  req.SetRequestURI(hr.Url)

  // 记录开始时间
  startTime := time.Now()
  // 开始请求
  err := client.Do(req, resp)
  // 计算响应时间差值
  requestTime := time.Since(startTime)
  response.RequestTime = requestTime.Milliseconds()
  response.Code = resp.StatusCode()
  if err != nil {
    response.Response = err.Error()
    return
  }
  log.Logger.Debug("resp:    ", string(resp.Body()))
  response.Response = string(resp.Body())

}

// 新建一个http客户端
func newHttpClient(httpClientSettings HttpClientSettings) (httpClient *fasthttp.Client) {
  // tls验证,关闭验证
  tr := &tls.Config{
    InsecureSkipVerify: true,
  }
  // 新建指针类型的客户端
  httpClient = &fasthttp.Client{}

  if httpClientSettings.Name != "" {
    httpClient.Name = httpClientSettings.Name
  }

  if httpClientSettings.NoDefaultUserAgentHeader == true {
    httpClient.NoDefaultUserAgentHeader = true
  }

  // 如果最大连接数不为0,将设置此数
  if httpClientSettings.MaxConnsPerHost != 0 {
    httpClient.MaxConnsPerHost = httpClientSettings.MaxConnsPerHost
  }

  // url不按照标准输出,按照原样输出
  if httpClientSettings.DisablePathNormalizing == true {
    httpClient.DisablePathNormalizing = true
  }
  // 请求头不按标准格式传输
  if httpClientSettings.DisableHeaderNamesNormalizing == true {
    httpClient.DisableHeaderNamesNormalizing = true
  }

  // 如果此时间不为0,那么将设置此时间。keep-alive维持此时长后将关闭。时间单位为毫秒
  if httpClientSettings.MaxConnDuration != 0 {
    httpClient.MaxConnDuration = time.Duration(httpClientSettings.MaxConnDuration) * time.Millisecond
  }

  if httpClientSettings.ReadTimeout != 0 {
    httpClient.ReadTimeout = time.Duration(httpClientSettings.ReadTimeout) * time.Millisecond
  }

  if httpClientSettings.WriteTimeout != 0 {
    httpClient.WriteTimeout = time.Duration(httpClientSettings.WriteTimeout) * time.Millisecond
  }

  // 该连接如果空闲的话,在此时间后断开。
  if httpClientSettings.MaxIdleConnDuration != 0 {
    httpClient.MaxIdleConnDuration = time.Duration(httpClientSettings.MaxIdleConnDuration) * time.Millisecond
  }

  //
  httpsTls := httpClientSettings.AdvancedOptions.Tls

  // 如果开启认证
  if httpsTls.IsVerify {
    // switch条件选择语句,如果认证类型为0:则表示双向认证,如果是1:则表示为单向认证
    switch httpsTls.VerifyType {
    case 0: // 开启双向验证
      tr.InsecureSkipVerify = false
      // 如果密钥文件为空则跳出switch语句
      if httpsTls.CaCert == "" {
        break
      }
      // 生成一个cert对象池
      caCertPool := x509.NewCertPool()
      if caCertPool == nil {
        fmt.Println("生成CertPool失败!")
        break
      }

      // 读取认证文件,读出后为字节
      key, err := ioutil.ReadFile(httpsTls.CaCert)
      // 如果读取错误,则跳出switch语句
      if err != nil {
        fmt.Println("打开密钥文件失败:", err.Error())
        break
      }
      // 将认证文件添加到cert池中
      ok := caCertPool.AppendCertsFromPEM(key)
      // 如果添加失败则跳出switch语句
      if !ok {
        fmt.Println("密钥文件错误,生成失败!!!")
        break
      }
      // 将认证信息添加到客户端认证结构体
      tr.ClientCAs = caCertPool
    case 1: // 开启单向验证,客户端验证服务端密钥
      tr.InsecureSkipVerify = false
    }
  }

  fmt.Println("tr:    ", tr.InsecureSkipVerify)
  // 客户端认证配置项
  httpClient.TLSConfig = tr
  return
}

// Header header
type Header struct {
  Field     string `json:"field"`      // 字段名称
  Value     string `json:"value"`      // 字段值
  FieldType string `json:"field_type"` // 字段类型
}

// Query query
type Query struct {
  Field     string `json:"field"`
  Value     string `json:"value"`
  FieldType string `json:"field_type"`
}

// Cookie cookie
type Cookie struct {
  Field     string `json:"field"`
  Value     string `json:"value"`
  FieldType string `json:"field_type"`
}

type HttpClientSettings struct {
  //  客户端的名称,在header中的user-agent使用,通常我们默认就好
  Name string `json:"name"`

  // 默认为flase,表示User-Agent使用fasthttp的默认值
  NoDefaultUserAgentHeader bool `json:"no_default_user_agent_header"`

  // 每台主机可以建立的最大连接数。如果没有设置,则使用DefaultMaxConnsPerHost。
  MaxConnsPerHost int `json:"max_conns_per_host"`

  // 空闲的保持连接在此持续时间之后关闭。默认情况下,在DefaultMaxIdleConnDuration之后关闭空闲连接。
  // 该连接如果空闲的话,在此时间后断开。
  MaxIdleConnDuration int64 `json:"max_idle_conn_duration"`

  // Keep-alive连接在此持续时间后关闭。默认情况下,连接时间是不限制的。
  MaxConnDuration int `json:"max_conn_duration"`

  // 默认情况下,响应读取超时时间是不限制的。
  ReadTimeout int64 `json:"read_timeout"`
  // 默认情况下,请求写超时时间不受限制。
  WriteTimeout int64 `json:"write_timeout"`

  // 请求头是否按标准格式传输
  DisableHeaderNamesNormalizing bool `json:"disable_header_names_normalizing"`
  // url路径是按照原样输出,还是按照规范化输出。默认按照规范化输出
  DisablePathNormalizing bool            `json:"disable_path_normalizing"`
  AdvancedOptions        AdvancedOptions `json:"advanced_options"` // 高级选项
}

// AdvancedOptions 高级选项
type AdvancedOptions struct {
  Tls Tls `json:"tls"` // 验证设置
}

// Tls tls认证结构体
type Tls struct {
  IsVerify   bool   `json:"is_verify"`   // 是否开启验证,默认不开启,开启后需要上传密钥文件
  VerifyType int32  `json:"verify_type"` // 认证类型:0表示双向认证;1表示单向认证;默认为0
  CaCert     string `json:"ca_cert"`     // 密钥文件
}

再在我们项目的根目录新建service/api.go,我们定义RunTestObject方法来实现我们的接口。


// Package service -----------------------------
// @file      : api.go
// @author    : 被测试耽误的大厨
// @contact   : 13383088061@163.com
// @time      : 2023/7/4 18:05
// -------------------------------------------
package service

import (
  "encoding/json"
  "fmt"
  "github.com/gin-gonic/gin"
  "github.com/google/uuid"
  "kitchen-engine/global/common"
  "kitchen-engine/global/log"
  "kitchen-engine/model"
  "net/http"
)


/*
  RunTestObject 实现run/testObject/接口
*/

func RunTestObject(c *gin.Context) {

  // 声明一个TO对象
  var testObject model.TestObject

  // 接收json格式的请求数据
  err := c.ShouldBindJSON(&testObject)
  id := uuid.New().String()
  // 如果请求格式错误
  if err != nil {
    log.Logger.Error("请求数据格式错误", err.Error())
    common.ReturnResponse(c, http.StatusBadRequest, id, "请求数据格式错误!", err.Error())
    return
  }

  // 使用json包解析以下TO对象, 解析出来为[]byte类型
  requestJson, _ := json.Marshal(testObject)
  // 打印以下日志, 使用fmt.Sprintf包格式化数据,%s 表示string(requestJson)为字符串类型,如果不确定类型,可以使用%v表示
  log.Logger.Debug(fmt.Sprintf("测试对象: %s", string(requestJson)))

  response := model.TestObjectResponse{
    Name:        testObject.Name,
    Id:          testObject.Id,
    ParentId:    testObject.ParentId,
    ObjectType:  testObject.ObjectType,
    ItemId:      testObject.ItemId,
    SourceId:    testObject.SourceId,
    ChannelId:   testObject.ChannelId,
    ChannelType: testObject.ChannelType,
  }

  // 开始处理TO
  testObject.Dispose(&response)
  // 返回响应消息
  common.ReturnResponse(c, http.StatusOK, id, "请求成功!", response)
  return
}

main.go如下:


func main() {
  log.Logger.Debug("yc:   ", config.YC)
  runService()
  log.Logger.Info("欢迎使用zap日志")
}

启动项目。然后我们使用其他接口测试工具对我们的接口进行调试。


method: POST
url: http:127.0.0.1:8003/engine/run/testObject/
body: 
{
    "name": "百度",
    "id": "12312312",
    "parent_id": "",
    "object_type": "HTTP1.1",
    "item_id": "",
    "http_request": {
        "url": "http://www.baidu.com",
        "method": "GET"
    }

}

image.png
响应信息如下:


{
  "code": 200,
  "id": "b531a6db-ba65-4bb4-b9cb-fb252a210996",
  "message": "请求成功!",
  "data": {
    "name": "百度",
    "id": "12312312",
    "parent_id": "",
    "object_type": "HTTP1.1",
    "item_id": "",
    "team_id": "",
    "source_id": "",
    "channel_id": "",
    "channel_type": "",
    "code": 200,
    "request_headers": null,
    "response_headers": null,
    "response": "***全球领先的中文搜索引擎、致力于让网民更便捷地获取信息,找到所求。百度超过千亿的中文网页数据库,可以瞬间找到相关的搜索结果***"
  }
}
相关文章
|
2月前
|
关系型数据库 MySQL 测试技术
【分享】AgileTC测试用例管理平台使用分享
AgileTC 是一个脑图样式测试用例管理平台,支持用例设计、执行与团队协作,帮助测试人员高效管理测试流程。
188 116
【分享】AgileTC测试用例管理平台使用分享
|
2月前
|
人工智能 数据可视化 测试技术
AI测试平台自动遍历:低代码也能玩转全链路测试
AI测试平台的自动遍历功能,通过低代码配置实现Web和App的自动化测试。用户只需提供入口链接或安装包及简单配置,即可自动完成页面结构识别、操作验证,并生成可视化报告,大幅提升测试效率,特别适用于高频迭代项目。
|
2月前
|
人工智能 测试技术 调度
写用例写到怀疑人生?AI 智能测试平台帮你一键生成!
霍格沃兹测试开发学社推出AI智能测试用例生成功能,结合需求文档一键生成高质量测试用例,大幅提升效率,减少重复劳动。支持自定义提示词、多文档分析与批量管理,助力测试人员高效完成测试设计,释放更多时间投入核心分析工作。平台已开放内测,欢迎体验!
|
2月前
|
人工智能 测试技术 项目管理
测试不再碎片化:AI智能体平台「项目资料套件」功能上线!
在实际项目中,需求文档分散、整理费时、测试遗漏等问题常困扰测试工作。霍格沃兹推出AI智能体测试平台全新功能——项目资料套件,可将多个关联文档打包管理,并一键生成测试用例,提升测试完整性与效率。支持套件创建、文档关联、编辑删除及用例生成,适用于复杂项目、版本迭代等场景,助力实现智能化测试协作,让测试更高效、更专业。
|
3月前
|
存储 人工智能 算法
AI测试平台实战:深入解析自动化评分和多模型对比评测
在AI技术迅猛发展的今天,测试工程师面临着如何高效评估大模型性能的全新挑战。本文将深入探讨AI测试平台中自动化评分与多模型对比评测的关键技术与实践方法,为测试工程师提供可落地的解决方案。
|
2月前
|
人工智能 自然语言处理 测试技术
AI测试平台的用例管理实践:写得清晰,管得高效,执行更智能
在测试过程中,用例分散、步骤模糊、回归测试效率低等问题常困扰团队。霍格沃兹测试开发学社推出的AI测试平台,打通“用例编写—集中管理—智能执行”全流程,提升测试效率与覆盖率。平台支持标准化用例编写、统一管理操作及智能执行,助力测试团队高效协作,释放更多精力优化测试策略。目前平台已开放内测,欢迎试用体验!
|
3月前
|
存储 人工智能 文字识别
从零开始打造AI测试平台:文档解析与知识库构建详解
AI时代构建高效测试平台面临新挑战。本文聚焦AI问答系统知识库建设,重点解析文档解析关键环节,为测试工程师提供实用技术指导和测试方法论
|
10月前
|
数据可视化 前端开发 测试技术
接口测试新选择:Postman替代方案全解析
在软件开发中,接口测试工具至关重要。Postman长期占据主导地位,但随着国产工具的崛起,越来越多开发者转向更适合中国市场的替代方案——Apifox。它不仅支持中英文切换、完全免费不限人数,还具备强大的可视化操作、自动生成文档和API调试功能,极大简化了开发流程。
|
5月前
|
Java 测试技术 容器
Jmeter工具使用:HTTP接口性能测试实战
希望这篇文章能够帮助你初步理解如何使用JMeter进行HTTP接口性能测试,有兴趣的话,你可以研究更多关于JMeter的内容。记住,只有理解并掌握了这些工具,你才能充分利用它们发挥其应有的价值。+
859 23
|
7月前
|
SQL 安全 测试技术
2025接口测试全攻略:高并发、安全防护与六大工具实战指南
本文探讨高并发稳定性验证、安全防护实战及六大工具(Postman、RunnerGo、Apipost、JMeter、SoapUI、Fiddler)选型指南,助力构建未来接口测试体系。接口测试旨在验证数据传输、参数合法性、错误处理能力及性能安全性,其重要性体现在早期发现问题、保障系统稳定和支撑持续集成。常用方法包括功能、性能、安全性及兼容性测试,典型场景涵盖前后端分离开发、第三方服务集成与数据一致性检查。选择合适的工具需综合考虑需求与团队协作等因素。
899 24