HttpRunner 从 v4.0 开始新增支持 WebSocket 协议。

本文将结合案例初步介绍使用 HttpRunner v4.0 测试 WebSocket 的方法,欢迎大家多多实践,后续我们将基于大家的反馈进行迭代优化。

功能概览

在 HttpRunner v4.0 中,当前针对 WebSocket 支持了如下能力:

案例演示

实践出真知,首先我们来看一个完整的测试用例。

该测试用例使用的被测服务是一个类似于 httpbin 的回显服务,协议类型是 WebSocket 协议:ws://echo.websocket.events。无论客户端发送了什么消息,服务端都会原封不动地返回,具体效果可以参考链接

测试用例总共包含了如下的 10 个步骤:

  1. 建立一个新的连接并携带请求头 "User-Agent": "HttpRunnerPlus",并且对响应状态码 status_code 和响应头 headers.Connection 的内容进行断言
  2. 进行一次 ping pong 连接测试,超时时间设为 5 秒
  3. 读取建立连接之后,服务端主动推送的赞助消息 "echo.websocket.events sponsored by Lob.com",并对 body 中包含的字符串内容进行断言,超时时间设为 5 秒
  4. 写消息,指定消息类型为文本类型,消息内容为 JSON 格式:{"foo1": "${gen_random_string($n)}", "foo2": "${max($a, $b)}"},注意该消息内容中用到了 HttpRunner 的内置函数 gen_random_stringmax
  5. 读消息,不需要指定消息类型,对 body.foo1 进行参数提取,保存为新的变量 varFoo1,随后再对 body.foo1 的长度和 body.foo2 的值进行断言
  6. 读写消息,写消息的类型为文本类型,消息内容为上一步中提取的 varFoo1,并对服务端返回的消息进行读取,并且对 body 的长度进行断言
  7. 读写消息,写消息的类型为二进制类型,消息内容为从本地文件导入的二进制数据,并对服务端返回的消息进行读取,但不进行任何参数提取和结果断言操作
  8. 写一段冗余的消息 "have a nice day!",用于测试断开连接前冗余消息的自动舍弃
  9. 写一段冗余的消息 "balabala ...",用于测试断开连接前冗余消息的自动舍弃
  10. 通知服务端断开当前连接,超时时间设为 30 秒,指定状态码为 1000(说明客户端是正常断开连接),并且对服务端返回的响应状态码进行断言

上述测试用例对应的脚本形式如下:

config:
  name: run request with WebSocket protocol
  base_url: ws://echo.websocket.events
  variables:
    a: 12.3
    b: 3.45
    file: "./demo_file_load_ws_message.txt"
    n: 5

teststeps:
- name: open connection
  websocket:
    type: open
    url: "/"
    headers:
      User-Agent: HttpRunnerPlus
  validate:
  - check: status_code
    assert: equals
    expect: 101
    msg: check open status code
  - check: headers.Connection
    assert: equals
    expect: Upgrade
    msg: check headers
- name: ping pong test
  websocket:
    type: ping
    url: "/"
    timeout: 5000
- name: read sponsor info
  websocket:
    type: r
    url: "/"
    timeout: 5000
  validate:
  - check: body
    assert: contains
    expect: Lob.com
    msg: check sponsor message
- name: write json
  websocket:
    type: w
    url: "/"
    text:
      foo1: "${gen_random_string($n)}"
      foo2: "${max($a, $b)}"
- name: read json
  websocket:
    type: r
    url: "/"
  extract:
    varFoo1: body.foo1
  validate:
  - check: body.foo1
    assert: length_equals
    expect: 5
    msg: check json foo1
  - check: body.foo2
    assert: equals
    expect: 12.3
    msg: check json foo2
- name: write and read text
  websocket:
    type: wr
    url: "/"
    text: "$varFoo1"
  validate:
  - check: body
    assert: length_equals
    expect: 5
    msg: check length equal
- name: write and read binary file
  websocket:
    type: wr
    url: "/"
    binary: "${load_ws_message($file)}"
- name: write something redundant
  websocket:
    type: w
    url: "/"
    text: have a nice day!
- name: write something redundant
  websocket:
    type: w
    url: "/"
    text: balabala ...
- name: close connection
  websocket:
    type: close
    url: "/"
    close_status: 1000
    timeout: 30000
  validate:
  - check: status_code
    assert: equals
    expect: 1000
    msg: check close status code

需要说明的是,该测试用例中的示例文件需要由用户自己来指定,文件路径填写有效的绝对路径或相对路径即可,这里的示例文件 demo_file_load_ws_message.txt 只是以 txt 文件为例,实际上该文件可以为任意类型,最终导入之后都为二进制类型。

除了 YAML/JSON 格式外,我们也可以采用 gotest 的方式编写用例,形式如下:

package tests

import (
   "testing"

   "github.com/httprunner/httprunner/v4/hrp"
)

func TestWebSocketProtocol(t *testing.T) {
   testcase := &hrp.TestCase{
      Config: hrp.NewConfig("run request with WebSocket protocol").
         SetBaseURL("ws://echo.websocket.events").
         WithVariables(map[string]interface{}{
            "n":    5,
            "a":    12.3,
            "b":    3.45,
            "file": "./demo_file_load_ws_message.txt",
         }),
      TestSteps: []hrp.IStep{
         hrp.NewStep("open connection").
            WebSocket().
            OpenConnection("/").
            WithHeaders(map[string]string{"User-Agent": "HttpRunnerPlus"}).
            Validate().
            AssertEqual("status_code", 101, "check open status code").
            AssertEqual("headers.Connection", "Upgrade", "check headers"),
         hrp.NewStep("ping pong test").
            WebSocket().
            PingPong("/").
            WithTimeout(5000),
         hrp.NewStep("read sponsor info").
            WebSocket().
            Read("/").
            WithTimeout(5000).
            Validate().
            AssertContains("body", "Lob.com", "check sponsor message"),
         hrp.NewStep("write json").
            WebSocket().
            Write("/").
            WithTextMessage(map[string]interface{}{"foo1": "${gen_random_string($n)}", "foo2": "${max($a, $b)}"}),
         hrp.NewStep("read json").
            WebSocket().
            Read("/").
            Extract().
            WithJmesPath("body.foo1", "varFoo1").
            Validate().
            AssertLengthEqual("body.foo1", 5, "check json foo1").
            AssertEqual("body.foo2", 12.3, "check json foo2"),
         hrp.NewStep("write and read text").
            WebSocket().
            WriteAndRead("/").
            WithTextMessage("$varFoo1").
            Validate().
            AssertLengthEqual("body", 5, "check length equal"),
         hrp.NewStep("write and read binary file").
            WebSocket().
            WriteAndRead("/").
            WithBinaryMessage("${load_ws_message($file)}"),
         hrp.NewStep("write something redundant").
            WebSocket().
            Write("/").
            WithTextMessage("have a nice day!"),
         hrp.NewStep("write something redundant").
            WebSocket().
            Write("/").
            WithTextMessage("balabala ..."),
         hrp.NewStep("close connection").
            WebSocket().
            CloseConnection("/").
            WithTimeout(30000).
            WithCloseStatus(1000).
            Validate().
            AssertEqual("status_code", 1000, "check close status code"),
      },
   }

   // run testcase
   err = hrp.NewRunner(t).Run(testcase)
   if err != nil {
      t.Fatalf("run testcase error: %v", err)
   }
}

可以看出,WebSocket 协议的脚本形式与之前 HTTP(S) 协议的格式基本一致。如果大家使用 gotest 编写脚本,也可以借助「链式调用」获得方法提示。

操作类型介绍

WebSocket 不同于 HTTP 协议,不再使用 GET/POST/PUT/etc. 请求方法,而是包含 open/close/ping/w/r/wr 6 种操作类型。

下面对这 6 种操作类型进行介绍:

需要注意的是,6 种操作类型会存在一些限制差异,可查看如下对比表格:

操作类型 open ping w r wr close
支持设置超时时间
支持设置请求参数和请求头
是否为阻塞操作
支持发送消息
支持设置状态码
支持参数提取和结果断言

注意事项

另外,在测试 WebSocket 协议的时候还需注意如下事项:

What's next

本文作者:卜卜星,HttpRunner 核心开发者,贡献了本文介绍的 WebSocket 协议测试能力
HttpRunner 项目官网: https://httprunner.com/
如果 HttpRunner 对你有过帮助,麻烦帮忙给个 ⭐️star⭐️ 鼓励下吧
https://github.com/httprunner/httprunner


↙↙↙阅读原文可查看相关链接,并与作者交流