【测试平台系列】第一章 手撸压力机(八)- 实现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": "***全球领先的中文搜索引擎、致力于让网民更便捷地获取信息,找到所求。百度超过千亿的中文网页数据库,可以瞬间找到相关的搜索结果***"
  }
}
相关文章
|
13天前
|
人工智能 供应链 安全
AI辅助安全测试案例某电商-供应链平台平台安全漏洞
【11月更文挑战第13天】该案例介绍了一家电商供应链平台如何利用AI技术进行全面的安全测试,包括网络、应用和数据安全层面,发现了多个潜在漏洞,并采取了有效的修复措施,提升了平台的整体安全性。
|
19天前
|
JSON Java 测试技术
SpringCloud2023实战之接口服务测试工具SpringBootTest
SpringBootTest同时集成了JUnit Jupiter、AssertJ、Hamcrest测试辅助库,使得更容易编写但愿测试代码。
52 3
|
22天前
|
监控 安全 测试技术
构建高效的精准测试平台:设计与实现指南
在软件开发过程中,精准测试是确保产品质量和性能的关键环节。一个精准的测试平台能够自动化测试流程,提高测试效率,缩短测试周期,并提供准确的测试结果。本文将分享如何设计和实现一个精准测试平台,从需求分析到技术选型,再到具体的实现步骤。
95 1
|
2月前
|
人工智能 监控 测试技术
云应用开发平台测试
云应用开发平台测试
63 2
|
22天前
|
监控 安全 测试技术
构建高效精准测试平台:设计与实现全攻略
在软件开发过程中,精准测试是确保产品质量的关键环节。一个高效、精准的测试平台能够自动化测试流程,提高测试覆盖率,缩短测试周期。本文将分享如何设计和实现一个精准测试平台,从需求分析到技术选型,再到具体的实现步骤。
48 0
|
2月前
|
JSON 算法 数据可视化
测试专项笔记(一): 通过算法能力接口返回的检测结果完成相关指标的计算(目标检测)
这篇文章是关于如何通过算法接口返回的目标检测结果来计算性能指标的笔记。它涵盖了任务描述、指标分析(包括TP、FP、FN、TN、精准率和召回率),接口处理,数据集处理,以及如何使用实用工具进行文件操作和数据可视化。文章还提供了一些Python代码示例,用于处理图像文件、转换数据格式以及计算目标检测的性能指标。
68 0
测试专项笔记(一): 通过算法能力接口返回的检测结果完成相关指标的计算(目标检测)
|
3月前
|
移动开发 JSON Java
Jmeter实现WebSocket协议的接口测试方法
WebSocket协议是HTML5的一种新协议,实现了浏览器与服务器之间的全双工通信。通过简单的握手动作,双方可直接传输数据。其优势包括极小的头部开销和服务器推送功能。使用JMeter进行WebSocket接口和性能测试时,需安装特定插件并配置相关参数,如服务器地址、端口号等,还可通过CSV文件实现参数化,以满足不同测试需求。
252 7
Jmeter实现WebSocket协议的接口测试方法
|
3月前
|
JSON 移动开发 监控
快速上手|HTTP 接口功能自动化测试
HTTP接口功能测试对于确保Web应用和H5应用的数据正确性至关重要。这类测试主要针对后台HTTP接口,通过构造不同参数输入值并获取JSON格式的输出结果来进行验证。HTTP协议基于TCP连接,包括请求与响应模式。请求由请求行、消息报头和请求正文组成,响应则包含状态行、消息报头及响应正文。常用的请求方法有GET、POST等,而响应状态码如2xx代表成功。测试过程使用Python语言和pycurl模块调用接口,并通过断言机制比对实际与预期结果,确保功能正确性。
263 3
快速上手|HTTP 接口功能自动化测试
|
3月前
|
JavaScript 前端开发 测试技术
ChatGPT与接口测试
ChatGPT与接口测试,测试通过
51 5
|
2月前
|
JavaScript 前端开发 API
vue尚品汇商城项目-day02【9.Home组件拆分+10.postman测试接口】
vue尚品汇商城项目-day02【9.Home组件拆分+10.postman测试接口】
41 0