上一章节我们组合了场景,它是一个list结构。今天我们实现性能测试计划的数据结构及其方法。
首先,我们在model目录新建test_plan.go文件:
// Package model -----------------------------
// @file : test_plan.go
// @author : 被测试耽误的大厨
// @contact : 13383088061@163.com
// @time : 2023/8/14 12:06
// -------------------------------------------
package model
import (
"kitchen-engine/global/constant"
"time"
)
type TestPlan struct {
Id string `json:"id"` // 唯一id
Name string `json:"name"` // 场景名称
ItemId string `json:"item_id"` // 项目Id
TeamId string `json:"team_id"` // 团队Id
Task Task `json:"task"` // 具体的任务
TestScenes []TestScene `json:"test_scenes"` // 执行的场景列表
}
我们需要给测试计划配上具体的测试任务。
上述代码中的Task为测试任务结构,在model下新建task.go:
// Package model -----------------------------
// @file : task.go
// @author : 被测试耽误的大厨
// @contact : 13383088061@163.com
// @time : 2023/8/14 13:48
// -------------------------------------------
package model
/*
测试任务,我们先实现根据时长并发数模式
**/
type Task struct {
ConcurrentUsers int64 `json:"concurrent_users"` // 并发用户数
TimeType string `json:"time_type"` // 时间的类型 时:h 分:m 秒:s
Duration int64 `json:"duration"` // 持续时长
}
我们实现一个计划可以运行多个场景,所以使用[]TestScene列表的机构,这样我们一个计划就包含了多个场景。下面我们优化一下,上一章节中的TestScene结构体的Dispose()方法,优化后如下:
// 优化前
func (testScene TestScene) Dispose() {
// 从testScene.TestObjects的最外层开始循环
for _, testObject := range testScene.TestObjects {
// 声明一个waitGroup控制等待内层循环请求内部完成
wg := sync.WaitGroup{}
// 从一层级的list中读取每个TestObject对象
wg.Add(1)
go func(object TestObject) {
defer wg.Done()
response := TestObjectResponse{
Name: object.Name,
Id: object.Id,
SceneId: testScene.Id,
ItemId: object.ItemId,
ObjectType: object.ObjectType,
}
// 开始进行请求处理
object.Dispose(&response)
}(testObject)
// 等待内层的list完成后,再开始下一次外层循环
wg.Wait()
}
}
// 优化后
func (testScene TestScene) Dispose() {
// 从testScene.TestObjects的最外层开始循环
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)
// 从一层级的list中读取每个TestObject对象
}
}
下面,我们实现一下TestPlan的Dispose函数, test_plan.go:
// 处理测试计划
func (testPlan TestPlan) Dispose() {
duration := time.Duration(testPlan.Task.Duration)
// 将时分转换为秒, 默认为秒
switch testPlan.Task.TimeType {
case constant.H:
duration = duration * 3600
case constant.M:
duration = duration * 60
}
users := testPlan.Task.ConcurrentUsers
testScenes := testPlan.TestScenes
// 使用协程同时处理计划中的所有场景
for _, testScene := range testScenes {
go disposePlan(users, duration, testScene)
}
}
// 开始进行并发处理
func disposePlan(users int64, duration time.Duration, testScene TestScene) {
// 启动所有的用户,每个协程代表一个用户
for i := int64(0); i < users; i++ {
// 每个协程都配备一个定时器,时间到后,自动停止任务
timer := time.NewTimer(duration * time.Second)
go func() {
// 使用for循环和select进行定时控制
for {
select {
case <-timer.C: // 当到达持续时间后,停止任务
log.Logger.Debug("定时器结束。。。。。。。。。。。。。。。。。")
timer.Stop() // 先把定时器停止掉,防止内泄露
return // 退出
default:
testScene.Dispose()
}
}
}()
}
}
这样,我们就可以简单的实现的性能任务测试了,下面我们实现一下testPlan的api,方便我们通过接口进行调用。
在service目录下新建stress_test_plan.go
// Package service -----------------------------
// @file : stress_test_plan.go
// @author : 被测试耽误的大厨
// @contact : 13383088061@163.com
// @time : 2023/8/14 14:23
// -------------------------------------------
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"
)
func StressRunTestPlan(c *gin.Context) {
// 声明一个TO对象
var testPlan model.TestPlan
// 接收json格式的请求数据
err := c.ShouldBindJSON(&testPlan)
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(testPlan)
// 打印以下日志, 使用fmt.Sprintf包格式花数据,%s 表示string(requestJson)为字符串类型,如果不确定类型,可以使用%v表示
log.Logger.Debug(fmt.Sprintf("测试对象: %s", string(requestJson)))
// 如果场景为空,直接返回
if testPlan.Task.ConcurrentUsers <= 0 {
common.ReturnResponse(c, http.StatusBadRequest, id, "请求错误!", "并发数不能小于等于0")
return
}
if testPlan.Task.Duration <= 0 {
common.ReturnResponse(c, http.StatusBadRequest, id, "请求错误!", "运行时间不能小于等于0")
return
}
if len(testPlan.TestScenes) <= 0 || len(testPlan.TestScenes[0].TestObjects) <= 0 {
common.ReturnResponse(c, http.StatusBadRequest, id, "请求错误!", "计划中的场景不能为空")
return
}
// 开始处理TP
testPlan.Dispose()
// 返回响应消息
common.ReturnResponse(c, http.StatusOK, id, "请求成功!", "success")
return
}
router.go
// Package routers -----------------------------
// @file : router.go
// @author : 被测试耽误的大厨
// @contact : 13383088061@163.com
// @time : 2023/6/29 13:54
// -------------------------------------------
package routers
import (
"github.com/gin-gonic/gin"
"kitchen-engine/service"
)
/*
路由配置
*/
func initRouters(groups *gin.RouterGroup) {
{
groups.POST("/run/testObject/", service.RunTestObject) //运行测试对象接口, url: http://*****/engine/run/testObject/
groups.POST("/run/testScene/", service.RunTestScene) //运行场景接口, url: http://*****/engine/run/testObject/
groups.POST("/run/stress/testPlan/", service.StressRunTestPlan) //运行性能测试计划接口, url: http://*****/engine/run/stress/testPlan/
}
}
我们在根目录下新建expample-json目录,这里存放我们请求本服务的json文件。
运行对象调试的json: object.json
接口: http://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": {}
}
}
运行场景调试的json:scene.json
接口:http://127.0.0.1:8002/engine/run/testScene/
{
"name": "调试百度",
"id": "dlsjflksdjflks",
"item_id": "12312312312312",
"team_id": "1231231231231",
"test_objects": [
{
"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": {}
}
}
]
}
运行性能测试计划json: plan.json
接口: http://127.0.0.1:8002/engine/run/stress/testPlan/
{
"id": "lkjflksjlfjsjlflsfjfskldj",
"name": "性能测试百度接口",
"item_id": "12312312312312",
"team_id": "1231231231231",
"task": {
"concurrent_users": 5,
"time_type": "s",
"duration": 5
},
"test_scenes": [
{
"name": "调试百度",
"id": "dlsjflksdjflks",
"item_id": "12312312312312",
"team_id": "1231231231231",
"test_objects": [
{
"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": {}
}
}
]
}
]
}
大家,可以在自己电脑运行一下,使用访问一下这几个接口试试。