【测试平台系列】第一章 手撸压力机(十二)-初步实现提取功能

简介: 上一章节,我们主要实现了基础的并发测试场景的能力。本章节,我们实现一下,如何对响应进行提取,使用正则/json对响应信息提取,并赋值给我们定义的变量。

上一章节,我们主要实现了基础的并发测试场景的能力。本章节,我们实现一下,如何对响应进行提取,使用正则/json对响应信息提取,并赋值给我们定义的变量。
首先定义一个提取的数据结构。model目录下新建withdraw.go


// Package model -----------------------------
// @file      : withdraw.go
// @author    : 被测试耽误的大厨
// @contact   : 13383088061@163.com
// @time      : 2023/8/15 15:59
// -------------------------------------------
package model

type Withdraws struct {
        WithdrawList []*Withdraw json:"withdraw_list,omitempty" // 提取式一个列表
}

type Withdraw struct {
        IsEnable   bool        json:"is_enable,omitempty"  // 是否启用
        Key        string      json:"key,omitempty"        // 赋值给->变量名
        Type       int32       json:"type,omitempty"       // 提取类型: 0: 正则表达式    1: json  2:提取响应头    3:提取响应码 默认是0
        Index      int         json:"index,omitempty"      // 需要提取指的下标
        Value      interface{} json:"value,omitempty"      // 提取出来的值
        Expression string      json:"expression,omitempty" // 表达式
        ValueType  string      json:"value_type,omitempty" // 提取出来的值的类型
}

我们针对提取类型,定义一下常量global/constant/constant.go

// 关联提取类型
const (
        RegExtract    = 0 // 正则提取
        JsonExtract   = 1 // json提取
        HeaderExtract = 2 // 提取header
        CodeExtract   = 3 // 响应码提取
)

然后,我们实现一下Withdraw结构体的提取方法model/withdraw.go

// http请求提取
func (wd *Withdraw) WithdrawHttp(resp *fasthttp.Response, variable *sync.Map) {
        // 定义一个任意类型的变量,用来接收提取的值
        var value interface{}
        // 根据提取类型判断
        switch wd.Type {
        case constant.JsonExtract:
                var str string
                if resp != nil {
                        str = string(resp.Body())
                }
                // json.Withdraw,自己实现的json提取方法
                value = wd.jsonWithdraw(str)
        case constant.CodeExtract:
                if resp != nil {
                        value = resp.StatusCode()
                } else {
                        value = ""
                }
        case constant.HeaderExtract:
                var str string
                if resp != nil {
                        str = resp.Header.String()
                }
                value = wd.jsonWithdraw(str)
        default:
                var str string
                if resp != nil {
                        str = string(resp.Body())
                }
                value = wd.regexWithdraw(str)
        }
        variable.Store(wd.Key, value)
}

// json提取
func (wd *Withdraw) jsonWithdraw(source string) interface{} {
        gq := gjson.Get(source, wd.Expression)
        return gq.Value()
}

// 正则提取
func (wd *Withdraw) regexWithdraw(source string) (value interface{}) {
        if wd.Expression == "" || source == "" {
                value = ""
                return
        }
        value = tools.FindAllDestStr(source, wd.Expression)
        if value == nil || len(value.([][]string)) < 1 {
                value = ""
        } else {
                if len(value.([][]string)[0]) <= 1 {
                        value = value.([][]string)[0][0]
                } else {
                        value = value.([][]string)[0][1]
                }

        }
        return
}

// header提取,使用正则表达式提取
func (wd *Withdraw) headerWithdraw(source string) (value interface{}) {
        if wd.Expression == "" || source == "" {
                return ""
        }
        return tools.MatchString(source, wd.Expression, wd.Index)
}

这样,我们有提取的时候,直接根据提取类型,去使用Withdraws的不同的方法即可。
下面,我们完成http请求的关联提取,首先给HttpRequest结构体添加Withdraws字段,使用Withdraws指针类型

// HttpRequest http请求的结构
type HttpRequest struct {
        Url                string             json:"url"               // 接口uri
        Method             string             json:"method"            // 接口方法,Get Post Update...
        Timeout            int64              json:"timeout,omitempty" // 请求超时时长,默认为30秒,防止请求阻塞
        Body               *Body              json:"body,omitempty"    // 请求提
        Headers            []Header           json:"headers,omitempty" // 接口请求头
        Querys             []Query            json:"querys,omitempty"  // get请求时的url
        Cookies            []Cookie           json:"cookies,omitempty" // cookie
        Withdraws          *Withdraws         json:"withdraws,omitempty"
        HttpClientSettings HttpClientSettings json:"http_client_settings" // http客户端配置
}

然后,我们实现HttpRequest结构体的withdraw方法

// http提取
func (hr *HttpRequest) withdraw(resp *fasthttp.Response, variableMap *sync.Map) {
        if hr.Withdraws == nil {
                return
        }
        if hr.Withdraws.WithdrawList == nil {
                return
        }
        for _, withdraw := range hr.Withdraws.WithdrawList {
                if !withdraw.IsEnable {
                        continue
                }
                withdraw.WithdrawHttp(resp, variableMap)
        }
}

因为我们需要将提取出来的值存放到一个公共的地方,给其他接口去使用,所以我们使用一个带锁的sync.Map来存储提取出来的变量,这样就避免了map的并发安全问题。
修改HttpRequest结构体的Request方法,修改后如下,http_request.go:

func (hr *HttpRequest) Request(response *TestObjectResponse, variableMap *sync.Map) {
        // 使用fasthttp 协程池

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

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

        // 设置请求方法
        hr.methodInit(req)

        // 设置header
        hr.headerInit(req)
        // 设置query
        hr.queryInit(req)
        // 设置cookie
        hr.cookieInit(req)

        // 设置url
        hr.urlInit(req)

        // 设置body
        hr.bodyInit(req)

        // 设置请求超时时间
        hr.setReqTimeout(req)

        // 记录开始时间
        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()
        } else {
                // 如果响应中有压缩类型,防止压缩类型出现乱码
                switch string(resp.Header.ContentEncoding()) {
                case "br", "deflate", "gzip":
                        b, _ := resp.BodyUncompressed()
                        response.Response = string(b)
                default:
                        response.Response = string(resp.Body())
                }
        }
    // 关联提取
        hr.withdraw(resp, variableMap)
}

修改TestObject对象的Dispose方法如下:

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

修改TestScene对象的Dispose方法如下:

func (testScene TestScene) Dispose() {
   // 从testScene.TestObjects的最外层开始循环
   variableMap := &sync.Map{}
   for _, testObject := range testScene.TestObjects {
      response := &TestObjectResponse{
         Name:       testObject.Name,
         Id:         testObject.Id,
         SceneId:    testScene.Id,
         ItemId:     testObject.ItemId,
         ObjectType: testObject.ObjectType,
      }
      testObject.Dispose(response, variableMap)
      // 从一层级的list中读取每个TestObject对象
   }

}

修改/run/testObject/接口方法,分别添加一个全局sync.Map

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,
                ObjectType: testObject.ObjectType,
                ItemId:     testObject.ItemId,
                TeamId:     testObject.TeamId,
        }

        variableMap := &sync.Map{}
        // 开始处理TO
        testObject.Dispose(&response, variableMap)
        variableMap.Range(func(key, value any) bool {
                log.Logger.Debug(fmt.Sprintf("提取出来:   key: %s, value: %v ", key, value))
                return true
        })
        // 返回响应消息
        common.ReturnResponse(c, http.StatusOK, id, "请求成功!", response)
        return
}

现在启动我们的服务,我们使用下面的body调用我们的/run/testObject/接口:

接口:  127.0.0.1:8002/engine/run/testObject/
{
    "name": "百度",
    "id": "12312312312312",
    "object_type": "HTTP1.1",
    "item_id": "12312312312312",
    "team_id": "1231231231231",
    "http_request": {
        "url": "http://www.baidu.com",
        "method": "GET",
        "request_timeout": 5,
        "headers": [],
        "querys": [],
        "cookies": [],
        "http_client_settings": {},
        "withdraws": {
            "withdraw_list": [
                {
                    "is_enable": true,
                    "key": "name",
                    "type": 0,
                    "expression": "<meta name=\"description\" content=\"(.*?)\">"
                }
            ]
        }
    }

}

打印的结果如下:

2023-08-17T11:48:46.961+0800    DEBUG   service/object_api.go:43        测试对象: {"name":"百度","id":"12312312312312","object_type":"HTTP1.1","item_id":"12312312312312","team_id":"1231231231231","http_request":{"url":"http://www.baidu.com","method":"GET","withdraws":{"withdraw_list":[{"is_enable":true,"key","expression":"\u003cmeta name=\"description\" content=\"(.*?)\"\u003e"}]},"http_client_settings":{"name":"","no_default_user_agent_header":false,"max_conns_per_host":0,"max_idle_conn_duration":0,"max_conn_duration":0,"read_timeout":0,"write_timeout":0,"disable_header_names_normalizing":false,"disable_path_normalizing":false,"advanced_options":{"tls":{"is_verify":false,"verify_type":0,"ca_cert":""}}}}}
2023-08-17T11:48:47.244+0800    DEBUG   service/object_api.go:57        提取出来:   key: name, value: 全球领先的中文搜索引擎、致力于让网民更便捷地获取信息,找到所求。百度超过千亿的中文网页数据库,可以瞬间找到相关的搜索结果。

可以看到我们提取出来的值为:“全球领先的中文搜索引擎、致力于让网民更便捷地获取信息,找到所求。百度超过千亿的中文网页数据库,可以瞬间找到相关的搜索结果”,并将其赋值给name变量。

相关文章
|
2月前
|
运维
【运维基础知识】用dos批处理批量替换文件中的某个字符串(本地单元测试通过,部分功能有待优化,欢迎指正)
该脚本用于将C盘test目录下所有以t开头的txt文件中的字符串“123”批量替换为“abc”。通过创建批处理文件并运行,可实现自动化文本替换,适合初学者学习批处理脚本的基础操作与逻辑控制。
151 56
|
13天前
|
人工智能 供应链 安全
AI辅助安全测试案例某电商-供应链平台平台安全漏洞
【11月更文挑战第13天】该案例介绍了一家电商供应链平台如何利用AI技术进行全面的安全测试,包括网络、应用和数据安全层面,发现了多个潜在漏洞,并采取了有效的修复措施,提升了平台的整体安全性。
|
22天前
|
监控 安全 测试技术
构建高效的精准测试平台:设计与实现指南
在软件开发过程中,精准测试是确保产品质量和性能的关键环节。一个精准的测试平台能够自动化测试流程,提高测试效率,缩短测试周期,并提供准确的测试结果。本文将分享如何设计和实现一个精准测试平台,从需求分析到技术选型,再到具体的实现步骤。
95 1
|
2月前
|
人工智能 监控 测试技术
云应用开发平台测试
云应用开发平台测试
63 2
|
22天前
|
监控 安全 测试技术
构建高效精准测试平台:设计与实现全攻略
在软件开发过程中,精准测试是确保产品质量的关键环节。一个高效、精准的测试平台能够自动化测试流程,提高测试覆盖率,缩短测试周期。本文将分享如何设计和实现一个精准测试平台,从需求分析到技术选型,再到具体的实现步骤。
48 0
|
2月前
|
测试技术
Appscan手工探索、手工测试功能实战
Appscan手工探索、手工测试功能实战
|
3月前
|
JSON 移动开发 监控
快速上手|HTTP 接口功能自动化测试
HTTP接口功能测试对于确保Web应用和H5应用的数据正确性至关重要。这类测试主要针对后台HTTP接口,通过构造不同参数输入值并获取JSON格式的输出结果来进行验证。HTTP协议基于TCP连接,包括请求与响应模式。请求由请求行、消息报头和请求正文组成,响应则包含状态行、消息报头及响应正文。常用的请求方法有GET、POST等,而响应状态码如2xx代表成功。测试过程使用Python语言和pycurl模块调用接口,并通过断言机制比对实际与预期结果,确保功能正确性。
263 3
快速上手|HTTP 接口功能自动化测试
|
4月前
|
XML Web App开发 数据挖掘
Postman接口测试工具全解析:功能、脚本编写及优缺点探讨
文章详细分析了Postman接口测试工具的功能、脚本编写、使用场景以及优缺点,强调了其在接口自动化测试中的强大能力,同时指出了其在性能分析方面的不足,并建议根据项目需求和个人偏好选择合适的接口测试工具。
123 1
|
4月前
|
测试技术 Android开发 iOS开发
Appium 是一个开源的自动化测试框架,它支持多种平台和多种编程语言
Appium是一款开源自动化测试框架,支持iOS和Android多平台及多种编程语言。通过WebDriver协议,开发者可编写自动化测试脚本。在iPhone上实现屏幕点击等操作需安装Appium及其依赖,启动服务器,并设置所需的测试环境参数。利用Python等语言编写测试脚本,模拟用户交互行为,最后运行测试脚本来验证应用功能。对于iPhone测试,需准备真实设备或Xcode模拟器。
126 1
|
4月前
|
Web App开发 敏捷开发 测试技术
自动化测试之美:使用Selenium WebDriver进行网页功能验证
【8月更文挑战第29天】在数字时代,软件质量是企业竞争力的关键。本文将深入探讨如何通过Selenium WebDriver实现自动化测试,确保网页应用的可靠性和性能。我们将从基础设置到编写测试用例,逐步引导读者掌握这一强大的测试工具,同时分享实战经验,让测试不再是开发的负担,而是质量保证的利器。