Locust 脚本开发入门(3)

脚本开发入门

在前面的两节里面,我们已经演示了一个 Locust 的:

  • 脚本的基本构成
  • 脚本的初始化:on_start
  • 脚本的任务规划:通过 @task 装饰器实现
  • 任务的控制:按权重执行、按顺序执行
  • 等待的控制:任务之间的3种间隔、步骤之间采用 time 的 sleep
  • 响应的解析:状态码、响应正文(requests 库)
  • Web UI 中发起压测

本节内容主要是对 Locust 脚本种实现 HTTP 请求的进一步演示

注意:如果你已经比较了解 requests 库本节可以忽略跳过。


在绝大部分的测试场景下,HTTP 请求需要进行一些处理才能满足我们的测试需求,例如:

  • 在调试过程中,打印 HTTP 响应信息
  • 对 HTTP 响应进行断言 / 结果标记
  • 修改 Locust 发出的默认 HTTP 请求头信息,以满足系统的要求
  • 对 HTTP 的 接口响应(Json 格式)进行解析

范例:输出响应调试信息

在下面的脚本中,你可以多尝试几个 host,执行这个脚本,看看 Locust 打印的调试信息和你浏览器反馈的页面是不是一致的

from locust import HttpUser, task, constant, SequentialTaskSet

class TaskCase(SequentialTaskSet):
    @task
    def search_page(self):
        with self.client.get("/") as resp:
            
            # HTTP 响应状态码
            print(resp.status_code)
            
            # HTTP 响应头
            print(resp.headers)  
            
            # HTTP 响应正文(需要对文本字符串做提取时)
            print(resp.text)  
            
            # HTTP 响应正文(需要对文件、图片做提取时)
            print(resp.content)  
            
class customUser(HttpUser):
    tasks = [TaskCase]
    wait_time = constant(5)

把上面的脚本保存为:locustfile-1.py,执行命令:

PS E:\study.locust\Locust 脚本开发入门(3)> locust -f .\locustfile-1.py

为了方便我们观察,在 Web UI 中:

  1. 填入总生成用户数(Number of users to simulate)为1
  2. 加载速率1
  3. 主机域名:https://www.feixiaohao.com(也可以任意一个你喜欢的站点,因为这个脚本本身只是访问了站点的根目录而已)

开始执行场景,回到控制台,你可以看到:

PS E:\study.locust> locust -f .\locustfile3.py
[2020-07-08 14:07:07,697] DESKTOP-FOCB2DV/WARNING/locust.main: System open file limit setting is not high enough for load testing, and the OS wouldnt allow locust to increase it by itself. See https://docs.locust.io/en/stable/installation.html#increasing-maximum-number-of-open-files-limit for more info.
[2020-07-08 14:07:07,698] DESKTOP-FOCB2DV/INFO/locust.main: Starting web interface at http://:8089
[2020-07-08 14:07:07,706] DESKTOP-FOCB2DV/INFO/locust.main: Starting Locust 1.1
[2020-07-08 14:07:23,053] DESKTOP-FOCB2DV/INFO/locust.runners: Hatching and swarming 1 users at the rate 1 users/s (0 users already running)...
[2020-07-08 14:07:23,054] DESKTOP-FOCB2DV/INFO/locust.runners: All users hatched: cnblogUser: 1 (0 already running)
[2020-07-08 14:08:44,796] DESKTOP-FOCB2DV/INFO/locust.runners: Hatching and swarming 1 users at the rate 1 users/s (0 users already running)...
[2020-07-08 14:08:44,796] DESKTOP-FOCB2DV/INFO/locust.runners: All users hatched: cnblogUser: 1 (0 already running)
200
{'Server': 'nginx/1.18.0', 'Date': 'Wed, 08 Jul 2020 06:08:45 GMT', 'Content-Type': 'text/html; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'X-Powered-By': 'Express', 'Cache-Control': 'public, max-age=10', 'ETag': '"761c-PIKgr4JWI+oiEKT5C8QfDeZDmyI"', 'Vary': 'Accept-Encoding', 'Content-Encoding': 'gzip', 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains'}
<!doctype html>
<html data-n-head-ssr>
  <head >
    <title>非小号 - 比特币行情价格_专注数字货币行业大数据分析</title><meta data-n-head="ssr" content="IE=edge,chrome=1" http-equiv="X-UA-Compatible"><meta data-n-head="ssr" charset="utf-8"><meta data-n-head="ssr" name="applicab
……    

通过打印调试信息,你现在可以在 Locust 的命令行界面看到调试信息了,我建议你在脚本调试过程中只使用 1 个并发执行,避免大量并发引起调试信息的刷屏。

范例:对 HTTP 响应进行断言 / 结果标记**

1)基于状态码判断

当把 catch_response 参数 设置为 True 时,你可以手动控制在 Locust 的统计信息中报告的内容,例如下面的基于状态码判断

from locust import HttpUser, task, constant, SequentialTaskSet

class TaskCase(SequentialTaskSet):
    @task
    def search_page(self):
        with self.client.get("/", catch_response=True) as resp:
        
            # 如果响应状态码为 200,标记为成功
            if resp.status_code == 200:
                resp.success()
            else:
                # 否则输出响应报文进一步排查
                resp.failure(resp.text)
                
class customUser(HttpUser):
    tasks = [TaskCase]
    wait_time = constant(5)

2) 基于响应报文判断

基于响应的状态码判断业务请求是否正确处理不够严谨,错误页面并不一定会以404状态码返回,错误的业务响应通常也是200,所以,这时候你可能还想对响应报文做进一步校验以确定请求是否处理成功

from locust import HttpUser, task, constant, SequentialTaskSet

class TaskCase(SequentialTaskSet):
    @task
    def search_page(self):
        with self.client.get("/", catch_response=True) as resp:
        
            # 如果字符串 “test”,标记为成功
            if 'test' in resp.text:
                resp.success()
            else:
                # 否则输出响应报文进一步排查
                resp.failure(resp.text)

class customUser(HttpUser):
    tasks = [TaskCase]
    wait_time = constant(5)

3) 基于响应时间判断

有时候过慢的响应时间也可以算作一种不可接受的错误,譬如你肯定就无法忍受人工客服电话那头为了查询你的个人信息还让你等上个半分钟,那一头的呼叫中心也无法忍受为了查询一个用户信息导致坐席干等阻塞半分钟,所以还可以这样处理:

from locust import HttpUser, task, constant, SequentialTaskSet

class TaskCase(SequentialTaskSet):
    @task
    def search_page(self):
        with self.client.get("/", catch_response=True) as resp:
        
            # 如果响应时间大于 3 秒标记为响应失败
            if resp.elapsed.total_seconds() > 3:
                resp.failure("Request took too long")
            else:
                # 否则不做业务判断直接标记为成功
                resp.success()

class customUser(HttpUser):
    tasks = [TaskCase]
    wait_time = constant(5)
    

范例:自定义 Locust 发出的默认 HTTP 请求头信息,以满足系统的要求

一个 HTTP 请求不能被正确处理,无非三点原因:

  • HTTP 请求头错误,自定义一个 HTTP 请求头即可
  • HTTP 请求报文内容错误,检查 url、参数格式、业务参数
  • 会话状态丢失,检查 session、cookie,在 Locust 中大部分情况下都自动保持会话
from locust import HttpUser, task, constant, SequentialTaskSet

header_d = {
	'Host': 'test.demo.host',
	'Connection': 'keep-alive',
	'Referer': 'https://test.demo.host/'
	}

class TaskCase(SequentialTaskSet):
    @task
    def search_page(self):
        # 给 headers 传入 http 头信息即可
        with self.client.get("/", headers=header_d,catch_response=True) as resp:
        
            if resp.elapsed.total_seconds() > 3:
                resp.failure("Request took too long")
            else:
                resp.success()

class customUser(HttpUser):
    tasks = [TaskCase]
    wait_time = constant(5)

范例:对 HTTP 的 接口响应(Json 格式)进行解析

在对服务接口执行压测过程中,响应报文如果是以Json作为数据格式返回,取值会更加方便,以下面以接口为例:

https://dncapi.bqiapp.com/api/v2/events/menus?imei=web&webp=1

接口返回了以下的数据格式报文:

{    
    "data":{
        "menus":[
            {
                "id":5,
                "name":"公告/新闻",
                "color":null
            },
            {
                "id":9,
                "name":"上币",
                "color":null
            },
            {
                "id":10,
                "name":"分叉/交换",
                "color":null
            },
            {
                "id":8,
                "name":"产品发布",
                "color":null
            },
            {
                "id":3,
                "name":"空投",
                "color":null
            },
            {
                "id":4,
                "name":"令牌销毁/回购",
                "color":null
            }
        ],
        "optional":[
        ]
    },
    "code":200,
    "msg":"success"
}

假定我们现在需要对 json 当中的 code 进行取值,脚本范例(保存为:locustfile-3.py)

from locust import HttpUser, task, constant, SequentialTaskSet
import json
from jsonpath import jsonpath

class TaskCase(SequentialTaskSet):
    @task
    def search_page(self):
        with self.client.get("api/v2/events/menus?imei=web&webp=1", catch_response=True) as resp:
            resp_json = json.loads(resp.text)

            # 取 code 的值
            code_l = jsonpath(resp_json, '$.code')

            # 取 msg 的值
            msg_l = jsonpath(resp_json, '$.code')

            # 取 data -> menus -> 第一条数据的id
            id_index_0_l = jsonpath(resp_json, '$.data.menus.[0].id')

            # 取 data -> menus -> 所有数据的id
            id_index_all_l = jsonpath(resp_json, '$.data.menus.[*].id')

            print('code', code_l)
            print('msg', msg_l)
            print('id_index_0_l', id_index_0_l)
            print('id_index_all_l', id_index_all_l)


class customUser(HttpUser):
    tasks = [TaskCase]
    wait_time = constant(5)

这回执行命令

PS E:\study.locust> locust -f .\locustfile-3.py --headless -u 1 -r 1 --host https://dncapi.bqiapp.com/

然后查看命令行返回校验是否一致。

Locust 脚本开发入门(1)
Locust 脚本开发入门(2)
Locust 脚本开发入门(3)
Locust 脚本开发入门(4): 参数化
返回:教程目录
本节教程涉脚本

posted @ 2020-07-07 14:20  huanghaopeng  阅读(1262)  评论(1编辑  收藏  举报