原文由bugVanisher发表于TesterHome社区,点击原文链接可与作者间接交换。

前段时间,我主导推动组里实现了一套基于Locust+boomer的通用的压测平台,次要目标是满足咱们组内的各种压测场景,比方grpc、websocket、webrtc、http等协定的压测场景。正好咱们公司的技术栈以go为主,咱们能够轻松地应用go编写脚本,通过公司的部署平台编译打包后横向扩缩施压集群,能够说解决了各种压测需要。然而咱们发现,只管本人编写脚本十分自在,然而对不理解平台、不理解Go的同学来说,应用老本是比拟大的,尤其是首次接触,因而我开始思考如何简化脚本的编写和部署。

从http开始

来源于httprunner和公司其余Group的工具的灵感,我想到用json的形式去定义http压测场景,而后用go去解析执行,能够预感的是这种形式的压测性能不如间接写代码,然而如果能够通过可承受的性能损耗来换取更简略的接入形式,更对立的应用形式,也是极好的,毕竟咱们不缺机器。针对http协定,以下大略梳理了一下须要实现的能力。

简略阐明下:

  • 多接口

    很好了解,压测的时候须要满足多个接口按肯定的比例同时压测。在某些非凡场景下,可能还存在接口依赖的问题,这也要思考到。

  • 登录态

    压测的接口可能有登录校验,那么压测的时候须要带上登录态,如果能买通账号平台主动批量生成登录态就不便很多了。

  • 参数化

    定义的脚本须要提供参数化能力,总不能所有参数写死,比方动静生成工夫戳,ID,变长字符串等等,如果简略的参数生成无奈满足,用户本人上传也是挺好的。

  • 校验

    响应内容校验是接口测试很重要的局部,在压测场景也是一样的。

定义Json构造

接下来,定义Json构造,尽量去满足下面所形容的需要。http协定,无非就是三个局部,body、header、url,因而每一个接口须要蕴含这个三个字段,当然,名字是必不可少的,还有一个十分重要的字段,就是校验字段validator,上面就来看看这个Json应该是什么样子。

{    "debug": true,    "domain": "https://postman-echo.com",    "header": {},      "declare": [        "{{ $sessionId := getSid }}"    ],    "init_variables": {        "roomId": 1001,        "sessionId": "{{ $sessionId }}",        "ids": "{{ $sessionId }}"    },    "running_variables": {        "tid": "{{ getRandomId 5000 }}"    },    "func_set": [        {            "key": "getTest",            "method": "GET",            "url": "/get?name=gannicus&roomId={{ .roomId }}&age=10&tid={{ .tid }}",            "body": "{\"timeout\":10000}",            "validator": "{{ and  (eq .http_status_code 200) (eq .args.age (10 | toString )) }}"        },        {            "key": "postTest",            "method": "POST",            "header": {                "Cookie": "{{ .tid }}",                "Content-Type": "application/json"            },            "url": "/post?name=gannicus",            "body": "{\"timeout\":{{ .tid }}, \"retry\":true}",            "validator": "{{ and  (eq .http_status_code 200) (eq .data.timeout (.tid | toFloat64 ) ) (eq .data.retry false) }}"        }    ]}

func_set应该挺好了解的,这里解释一下declare、init_variables、running_variables:

  • declare
    这个字段是为了申明变量的,比方在init或running变量中都能够援用这个变量,申明形式如:

    [    "{{ $sessionId := getSid }}",    "{{ $id := 100100 }}"]
  • init_variables

    初始化变量,只初始化一次,能够是常量,也能够从模板函数中获取,如:

    {        "roomId": 1001,        "sessionId": "{{ $sessionId }}",        "ids": "{{ now }}"}
  • running_variables

    运行时变量,每一个申请发动前都会去结构参数,因而不倡议常量定义在这里。

    {        "tid": "{{ getRandomId 5000 }}"}

解析流程

想要利用boomer,那就须要想方法生成boomer.Task,它的构造如下:

type Task struct {    // The weight is used to distribute goroutines over multiple tasks.    Weight int    // Fn is called by the goroutines allocated to this task, in a loop.    Fn   func()    Name string}

外围就是失去这个执行函数Fn,思路就是别离依据init和running变量定义,为func_set中申明的每个申请别离定义一个匿名函数,函数中去动静生成变量,而后发动实在申请,最初依据每个申请申明的validator进行断言。整个执行流程如下:

实现原理

go的原生库中就有模板相干的库text/template,我间接应用模板库实现了这套解析逻辑,包含参数的生成,模板办法、断言,整个json脚本的语法都是基于go的模板库的。感兴趣的敌人能够查看:

  • 官网模板库
  • Go 语言规范库text/template 包深入浅出

如何断言

因为断言这部分十分重要,所以独自讲。下面曾经说过断言是通过模板来实现的,所以要应用断言就要把握根本的模板语法。

模板库内置了比拟和逻辑办法所以能够间接应用,比方比拟http状态码:

"validator": "{{ eq .http_status_code 200 }}"

再比方多个比拟:

"validator": "{{ and  (eq .http_status_code 200) (eq .data.timeout (.tid | toFloat64 ) ) (eq .data.retry false) }}"

可能你也曾经留神到了有一个toFloat64, 它是一个模板自定义函数,这里是为了做类型转换。

此外,你也能够看到基于go模板库,拜访变量变得非常简单,比方下面的.data.timeout,它对应响应中的内容相似如下:

{    "data":{        "timeout": 1000    }}

这样咱们就能够比拟响应json中的任何字段了。

写在最初

简略做了一下压力测试,比照不应用模板解析和应用模板解析的状况,模板解析在CPU密集型的场景下性能大略是间接写脚本编译的三分之一,如果不是CPU密集型应该能够去到二分之一,因而我临时不优化了。令人惊喜的是这个模板解析也能够扩大成为接口测试的编写形式,相似httprunner。

目前只是做了一个Demo,还没正真集成到咱们的压测平台,还是挺令人期待呀。

源码地址:https://github.com/bugVanisher/go-httpwrapper

原文由bugVanisher发表于TesterHome社区,点击原文链接可与作者间接交换。


今日份的常识已摄入~
想理解更多前沿测试开发技术:欢送关注「第十届MTSC大会·上海」>>>
1个主会场+12大专场,大咖星散精英齐聚
12个专场包含:
知乎、OpenHarmony、开源、游戏、酷家乐、音视频、客户端、服务端、数字经济、效力晋升、品质保障、智能化测试