问答 一个包含多个步骤的压力测试应该怎么用 Go 实现?

醋精测试媛 · 2024年03月01日 · 最后由 醋精测试媛 回复于 2024年03月11日 · 5362 次阅读

当我问 ChatGPT,一个包含多个步骤的压力测试应该怎么用 Go 实现……

我:请用 go 写一个压力测试案例,测试的是几百个不同的用户登录某个系统,每个用户登录都有其用户名和密码,登录步骤是先通过 http 请求,其请求正常的情况下会返回 mqtt 的代理信息,再以 mqtt 连接,如果 mqtt 连接成功,则登录成功。压力测试的目的是获取可以多少个用户登录该系统,请按照压力测试结果的分析过程,并根据分析过程提出一个调优方案。

ChatGPT代码使用了并发的 goroutine 来模拟多个用户同时登录系统每个用户发送一个 HTTP 请求来进行登录
然后根据返回的响应解析出 MQTT 代理信息最后尝试连接 MQTT 代理登录结果通过通道传递
并在主函数中打印出来您需要根据实际情况实现parseMQTTBrokerInfo和connectToMQTTBroker函数
来解析 MQTT 代理信息和连接到 MQTT 代理
请确保根据您的实际需求进行适当的修改和扩展包括错误处理并发控制和性能测试等方面
压力测试的目的是评估系统在高负载情况下的性能和稳定性以确定系统能够处理多少个并发用户
下面是一个分析压力测试结果的典型过程并根据分析提出调优方案的示例
1. 收集测试数据运行压力测试并记录每个用户登录的响应时间成功率和系统资源使用情况等数据
2. 分析响应时间检查每个用户登录的平均响应时间和响应时间分布关注是否存在异常的高延迟或慢速响应
如果存在明显的延迟问题可能需要优化系统的性能
3. 分析成功率检查登录成功的用户数量和总用户数量之比以确定系统在压力下的稳定性
如果成功率低于预期或出现错误率过高的情况可能需要调整系统设置增加资源或优化代码来提高稳定性
package main

import (
 "fmt"
 "net/http"
 "sync"
)

func main() {
 // 假设有500个用户登录
 numUsers := 500

 // 创建一个等待组,以便在所有用户登录完成后结束测试
 var wg sync.WaitGroup
 wg.Add(numUsers)

 // 创建一个通道用于接收登录结果
 resultCh := make(chan string)

 // 模拟用户登录
 for i := 0; i < numUsers; i++ {
  go func(userID int) {
   defer wg.Done()

   // 用户名和密码
   username := fmt.Sprintf("user%d", userID)
   password := fmt.Sprintf("password%d", userID)

   // 发起 HTTP 请求
   resp, err := http.Get("http://example.com/login?username=" + username + "&password=" + password)
   if err != nil {
    resultCh <- fmt.Sprintf("User %d: HTTP request error: %s", userID, err.Error())
    return
   }
   defer resp.Body.Close()

   // 检查 HTTP 响应
   if resp.StatusCode != http.StatusOK {
    resultCh <- fmt.Sprintf("User %d: Invalid HTTP response code: %d", userID, resp.StatusCode)
    return
   }

   // 解析 MQTT 代理信息
   // 这里假设从 HTTP 响应中解析出了 MQTT 代理信息
   mqttBroker := parseMQTTBrokerInfo(resp.Body)

   // 连接 MQTT
   // 这里假设使用某个 MQTT 客户端库进行连接
   err = connectToMQTTBroker(mqttBroker)
   if err != nil {
    resultCh <- fmt.Sprintf("User %d: MQTT connection error: %s", userID, err.Error())
    return
   }

   // 登录成功
   resultCh <- fmt.Sprintf("User %d: Logged in successfully", userID)
  }(i)
 }

 // 启动一个 goroutine 来等待所有用户登录完成并收集结果
 go func() {
  wg.Wait()
  close(resultCh)
 }()

 // 打印登录结果
 for result := range resultCh {
  fmt.Println(result)
 }

 // 测试结束
 fmt.Println("Testing completed.")
}

func parseMQTTBrokerInfo(body io.Reader) string {
 // 在这里实现从 HTTP 响应中解析出 MQTT 代理信息的逻辑
 // 返回 MQTT 代理的地址或其他必要信息
}

func connectToMQTTBroker(brokerInfo string) error {
 // 在这里实现连接到 MQTT 代理的逻辑
 // 返回连接错误或 nil
}

考虑到 Go 自带有一个轻量级的测试框架 testing 和自带的 go test 命令来实现性能测试,可以方便的获取性能指标,所以我提出使用 testing benchmark 和 go test 实现。

我:请用 benchmark 和 go test 再写一次该案例。

ChatGPT可以将代码保存为 login_test.go 文件
并在终端中运行 go test -bench=. -benchtime=10s 命令来执行压力测试
其中-bench=. -benchtime=10s 参数表示运行所有基准测试并将每个基准测试运行时间设置为 10 
 BenchmarkLogin 函数中我们使用 b.N 控制压力测试的迭代次数
在每次迭代中我们调用 loginUser 函数模拟用户登录并检查是否有错误发生
如果有错误我们使用 b.Fatalf 报告错误并中止测试
压力测试将运行多次迭代统计每次迭代的执行时间并输出结果
您可以根据需要调整迭代次数和测试时间
请注意这只是一个简单的示例您可能需要根据实际情况进行修改和扩展
例如添加更多的测试场景并发控制或其他性能指标的收集和分析等
package main

import (
 "fmt"
 "net/http"
 "testing"
)

// 用户结构体,包含用户名和密码
type User struct {
 Username string
 Password string
}

// 模拟用户登录
func loginUser(user User) error {
 // 发起 HTTP 请求
 resp, err := http.Get("http://example.com/login?username=" + user.Username + "&password=" + user.Password)
 if err != nil {
  return err
 }
 defer resp.Body.Close()

 // 检查 HTTP 响应
 if resp.StatusCode != http.StatusOK {
  return fmt.Errorf("Invalid HTTP response code: %d", resp.StatusCode)
 }

 // 解析 MQTT 代理信息
 // 这里假设从 HTTP 响应中解析出了 MQTT 代理信息
 mqttBroker := parseMQTTBrokerInfo(resp.Body)

 // 连接 MQTT
 // 这里假设使用某个 MQTT 客户端库进行连接
 err = connectToMQTTBroker(mqttBroker)
 if err != nil {
  return err
 }

 // 登录成功
 return nil
}

// 压力测试函数
func BenchmarkLogin(b *testing.B) {
 // 用户列表,包含多个用户
 users := []User{
  {Username: "user1", Password: "password1"},
  {Username: "user2", Password: "password2"},
  // 添加更多用户...
 }

 // 遍历用户列表进行登录
 for i := 0; i < b.N; i++ {
  // 获取当前用户
  user := users[i%len(users)]

  err := loginUser(user)
  if err != nil {
   b.Fatalf("Error during login: %s", err.Error())
  }
 }
}

阅读了代码之后,我有以下疑问:

  1. b.N 值是系统根据实际情况去调整的。所以users[i%len(users)]可能存在同一用户登录的情况。这种情况应该怎么避免或者应该怎么处理更好?
  2. 数据准备应该是不计划作为测试报告里面的,可以通过b.ResetTimer()先准备 n 个用户,但是作为一个多步骤的压力测试,从 HTTP 响应中解析 MQTT 代理信息这一步骤也是数据的处理,但是被放在了一次执行里面,请问这个步骤花费的时间能算在压力测试结果里面吗?如果不可以,应该怎么处理?
  3. 从本案例出发,ChatGPT 的做法有什么不足之处?或者它的处理是否不正确?
共收到 5 条回复 时间 点赞

讨论一下关于第二点我的理解:
你预设的所谓分步骤压力测试与 chatgpt 理解的压力测试并不一致;
你认为解析 MQTT 信息是一个数据的处理,那说明你想评估的是连接 MQTT 的性能;
而 chatgpt 理解的是评估登录的性能,请求并解析 MQTT 的代理的步骤也是登录的一部分;

Baoding 回复

明白了,因为 mqtt 的处理是包含在登录里面的,所以我应该把解析的步骤也当成登录的一部分。但是当这个解析的过程不同的处理,会影响压力测试的性能评定吗?

不了解有什么不同的处理;但是不是这些处理都是系统运行时的一部分,也同时是你评估性能时要覆盖的场景呢?

Baoding 回复

谢谢,这些处理是我自己的代码处理的,就像请求是自己封装的一样,可能会有一些反序列化、序列化之类的操作。但是可能确实是整个业务过程的一部分,是不能分开的。我只是担心如果这些操作测试代码如果写得繁琐,可能运行时间自然长了,和实际的场景肯定是不一致的。
所以我应该去问开发,实际场景中他们是怎么处理的,按照他们的处理来就可以了,应该把这个部分当成一次场景,而不是只抓这个地方纠结。

请问问题 1 应该怎么解决?

醋精测试媛 关闭了讨论 03月22日 16:07
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册