• 如何使用 Loadgen 来简化 HTTP API 请求的集成测试


    引言

    在编写 HTTP 服务的过程中,集成测试 1 是保证程序正确性的重要一环,如下图所示,其基本的流程就是不断向服务发起请求然后校验响应的状态和数据等:

    在这里插入图片描述

    为大量的 API 和用例编写测试是一件繁琐的工作,而 Loadgen 2 正是为了简化这一过程而设计的。

    一个简单的测试

    假定我们在 127.0.0.1:9100 端口监听了一个 Pizza 3 服务,现在我们通过如下配置来测试集合(collection)的创建:

    # loadgen.yml
    requests:
      - request:
          method: PUT
          url: http://127.0.0.1:9100/test_create_document
    
    • 1
    • 2
    • 3
    • 4
    • 5

    然后运行 loadgen -config loadgen.yml

    $ loadgen -config loadgen.yml
       __   ___  _      ___  ___   __    __
      / /  /___\/_\    /   \/ _ \ /__\/\ \ \
     / /  //  ///_\\  / /\ / /_\//_\ /  \/ /
    / /__/ \_//  _  \/ /_// /_\\//__/ /\  /
    \____|___/\_/ \_/___,'\____/\__/\_\ \/
    
    [LOADGEN] A http load generator and testing suite.
    [INF] warmup started
    [INF] loadgen is up and running now.
    [INF] [PUT] http://127.0.0.1:9100/test_create_document -
    [INF] status: 200, error: , response: {"success":true,"collection":"test_create_document"}
    [INF] warmup finished
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    为了便于阅读,笔者对程序输出进行了简化,实际会略有区别

    可以看到,Loadgen 实际上帮我们做了类似这样的操作:

    curl -XPUT http://127.0.0.1:9100/test_create_document
    
    • 1

    一些简单的测试

    上述示例中我们只测试了创建单个集合,但是实际情况下短时间内会有许多请求涌入,对于创建大量的集合我们又该如何测试呢?

    这里就需要用到变量 4 的概念:

    # loadgen.yml
    variables:
      - name: id
        type: sequence
    requests:
      - request:
          method: PUT
          url: http://127.0.0.1:9100/test_create_document_$[[id]]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    上述配置中,我们定义了一个名为 id 的变量,sequence 是一个特殊的类型——每次被读取时它的值会递增,因此 Loadgen 会不断发起类似这样的请求:

    curl -XPUT http://127.0.0.1:9100/test_create_document_0
    curl -XPUT http://127.0.0.1:9100/test_create_document_1
    curl -XPUT http://127.0.0.1:9100/test_create_document_2
    ...
    
    • 1
    • 2
    • 3
    • 4

    在 Pizza 的日志中也记录了这些请求:

    $ pizza
       ___ _____  __________   _
      / _ \\_   \/ _  / _  /  /_\
     / /_)/ / /\/\// /\// /  //_\\
    / ___/\/ /_   / //\/ //\/  _  \
    \/   \____/  /____/____/\_/ \_/
    
    [PIZZA] The Next-Gen Real-Time Hybrid Search & AI-Native Innovation Engine.
    [INFO] Collection test_create_document_0 created
    [INFO] Collection test_create_document_1 created
    [INFO] Collection test_create_document_2 created
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    不那么简单的测试

    目前为止,我们只是不断的向一个服务“塞”大量的请求,但比起发起请求,我们常常更关心程序的响应是否符合预期,也就是说,响应需要满足我们定义的一些条件,这可以通过 Loadgen 提供的 断言 5 功能来实现:

    # loadgen.yml
    variables:
      - name: id
        type: sequence
    runner:
      # 检查返回值是否正常
      assert_error: true
      # 检查断言是否通过
      assert_invalid: true
    requests:
      - request:
          method: PUT
          url: http://127.0.0.1:9100/test_create_document_$[[id]]
        assert:
          equals:
            # 注意,这里我们故意设置了一个“不正常”的值,以迫使断言失败
            _ctx.response.body_json.success: false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在上述配置中,我们启用了 Loadgen 的检查,然后定义了一个会失败的断言

    • equals 会校验给定路径 _ctx.response.body_json.success 是否与期望值 false 相等
    • _ctx.response.body_json 表示 JSON 格式的响应体
    • success 表示响应体中该字段对应的值,可以用 path.to.nested.key 来访问嵌套的字段

    也就是说,给定响应体 {"success":true,"collection":"test_create_document"},Loadgen 会检查 success 的值是否为 false

    $ loadgen -debug -r 1 -d 3 -config loadgen.yml
    #0 request, PUT http://127.0.0.1:9100/test_create_document_$[[id]], assertion failed, skiping subsequent requests
    [WRN] '_ctx.response.body_json.success' is not equal to expected value: true
    #0 request, PUT http://127.0.0.1:9100/test_create_document_$[[id]], assertion failed, skiping subsequent requests
    [WRN] '_ctx.response.body_json.success' is not equal to expected value: true
    #0 request, PUT http://127.0.0.1:9100/test_create_document_$[[id]], assertion failed, skiping subsequent requests
    [WRN] '_ctx.response.body_json.success' is not equal to expected value: true
    #0 request, PUT http://127.0.0.1:9100/test_create_document_$[[id]], assertion failed, skiping subsequent requests
    [WRN] '_ctx.response.body_json.success' is not equal to expected value: true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    上述命令我们使用了:

    • -debug 启用更详细的报错
    • -r 1 -d 3 减少发起的请求数(1req/s 持续 3s

    还有一个需要注意的细节是 ... is not equal to expected value: true,这里报告的是 success 字段实际的值,而不是断言中定义的期望值。

    可以看到,Loadgen 每次请求的断言都失败了,不过我们可以通过日志来快速定位出错的原因以便于调试。

    更进一步的测试

    现在我们创建了大量的空集合,是时候向其中添加一些文档(document)了,但是,一个首要解决的问题是,每次测试创建的集合名称是带有 $[[id]] 这个变量的,我们如何知道应该向哪个集合上传数据呢?一个可靠的解决方案是借助 Loadgen 的寄存器 6 功能:

    # loadgen.yml
    variables:
      - name: id
        type: sequence
    runner:
      assert_error: true
      assert_invalid: true
    requests:
      - request:
          method: PUT
          url: http://127.0.0.1:9100/test_create_document_$[[id]]
        assert:
          equals:
            _ctx.response.body_json.success: true
        register:
          # 把响应体的 collection 字段赋值给 $[[collection]]
          - collection: _ctx.response.body_json.collection
      - request:
          method: POST
          # 在上个请求创建的集合里添加一个文档
          url: http://127.0.0.1:9100/$[[collection]]/_doc
          body: '{"hello": "world"}'
        assert:
          equals:
            _ctx.response.body_json.result: created
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    上述示例中,我们利用动态注册的变量记录了每次测试创建的集合以便于后续请求使用。

    最后的优化

    为了使我们的配置更加灵活和“便携”,我们可以用环境变量来替换一些硬编码的值:

    # loadgen.yml
    variables:
      - name: id
        type: sequence
    runner:
      assert_error: true
      assert_invalid: true
    requests:
      - request:
          method: PUT
          # 读取 PIZZA_SERVER 这个环境变量
          url: $[[env.PIZZA_SERVER]]/test_create_document_$[[id]]
        assert:
          equals:
            _ctx.response.body_json.success: true
        register:
          - collection: _ctx.response.body_json.collection
      - request:
          method: POST
          url: $[[env.PIZZA_SERVER]]/$[[collection]]/_doc
          body: '{"hello": "world"}'
        assert:
          equals:
            _ctx.response.body_json.result: created
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    这样就可以通过:

    PIZZA_SERVER=http://127.0.0.1:9101 loadgen -config loadgen.yml
    
    • 1

    在不同的 Pizza 服务上运行测试。


    1. https://en.wikipedia.org/wiki/Integration_testing ↩︎

    2. https://www.infinilabs.com/docs/latest/gateway/getting-started/benchmark ↩︎

    3. https://www.infinilabs.com/en/docs/latest/pizza ↩︎

    4. https://www.infinilabs.com/docs/latest/gateway/getting-started/benchmark#变量的使用 ↩︎

    5. https://www.infinilabs.com/docs/latest/gateway/getting-started/benchmark#返回值判断 ↩︎

    6. https://www.infinilabs.com/docs/latest/gateway/getting-started/benchmark#动态变量注册 ↩︎

  • 相关阅读:
    【C++ 程序设计】实战:C++ 实践练习题(11~20)
    泛微 E-Office文件上传漏洞复现(CVE-2023-2523、CVE-2023-2648)
    Linux gpio驱动子系统剖析 | 01 - gpio子系统整体实现架构
    每日一题 2596. 检查骑士巡视方案
    阿里巴巴 Java性能诊断工具Arthas
    linux 中的 chmod 与 chown 命令
    黑马JVM总结(七)
    Node.js-express框架-cookie设置参数详解和举例
    一些性能优化的东西
    flask-socketio实现websocket通信
  • 原文地址:https://blog.csdn.net/infinilabs/article/details/134240912