Skip to content

第 3 章 · HttpUser 与任务编写

本章目标:掌握 HttpUser 的 client 属性用法(GET/POST/参数传递),学会用 name 参数聚合统计,理解 on_start 生命周期钩子。

3.1 HttpUser 的核心属性

python
from locust import HttpUser, task


class ApiUser(HttpUser):
    host = "https://httpbin.org"  # 默认目标主机

    @task
    def make_request(self):
        # self.client 是 HttpSession 实例
        resp = self.client.get("/get")
属性类型说明
clientHttpSession发 HTTP 请求的入口,API 兼容 requests
hoststr目标主机,可被命令行 -H 覆盖
environmentEnvironment当前测试环境对象(访问 runner、stats 等)

3.2 发送各类请求

self.client 支持全部 HTTP 方法:

python
@task
def http_methods(self):
    # GET 带查询参数
    self.client.get("/get", params={"page": 1, "size": 20})

    # POST 带 JSON 体
    self.client.post("/post", json={"username": "locust", "active": True})

    # PUT / DELETE / PATCH 同理
    self.client.put("/put", json={"id": 1})
    self.client.delete("/delete")
    self.client.head("/get")

带请求头:

python
@task
def with_headers(self):
    self.client.get(
        "/headers",
        headers={"Authorization": "Bearer my-token", "X-Custom": "value"},
    )

3.3 用 name 聚合统计

这是初学者最容易忽略但最重要的技巧。看下面的例子:

python
# ❌ 不推荐:每个商品 ID 会生成一条独立统计
self.client.get(f"/product/{product_id}")

# ✅ 推荐:所有商品页归入同一条 "/product" 统计
self.client.get(f"/product/{product_id}", name="/product")

如果不设 name,压测 100 个不同商品会产出 100 行统计记录——每行的样本量太小,百分位数据毫无参考价值。用 name 把同类请求合并后,统计才有意义。

python
@task
def browse_products(self):
    for product_id in range(10):
        self.client.get(
            f"/api/products/{product_id}",
            name="/api/products/[id]",  # 归组后的统计名
        )

3.4 校验响应

Locust 默认只把 HTTP 状态码非 2xx 记为失败。业务级校验需要手动处理:

python
from locust import HttpUser, task


class UserApi(HttpUser):
    host = "https://httpbin.org"

    @task
    def get_user(self):
        with self.client.get("/json", catch_response=True) as resp:
            if resp.status_code != 200:
                resp.failure(f"状态码异常: {resp.status_code}")
            elif "slideshow" not in resp.text:
                resp.failure("响应体缺少预期字段")
            else:
                resp.success()

关键点:

  • catch_response=True 让你接管成功/失败的判定权;
  • resp.failure("消息") 将该次请求标记为逻辑失败(即使 HTTP 200);
  • 这在验证 API 返回的业务码时必不可少。

REST API 校验模式

官方文档推荐对 JSON API 使用如下模板:先检查状态码,再解析 JSON 验证业务字段,最后在 else 分支标记成功。

3.5 on_start 与登录态管理

on_start 在每个虚拟用户启动时执行一次——是放置登录逻辑的标准位置:

python
class AuthenticatedUser(HttpUser):
    host = "https://httpbin.org"

    def on_start(self):
        """每个虚拟用户启动时调用一次"""
        resp = self.client.post(
            "/login",
            json={"username": "load_test", "password": "secret"},
        )
        token = resp.json().get("access_token")
        self.client.headers.update({"Authorization": f"Bearer {token}"})

    @task
    def access_protected(self):
        self.client.get("/bearer")  # 自动携带上面设置的 header

    def on_stop(self):
        """每个虚拟用户停止时调用一次"""
        pass

由于每个用户有独立的 client 实例,登录后设置的 headers 只影响当前用户——天然实现了会话隔离。

本章小结

  • client 支持 GET/POST/PUT/DELETE/PATCH/HEAD,API 兼容 requests;
  • name="/pattern" 把动态 URL 归入同一统计行,保证百分位数据有效;
  • catch_response=True + resp.failure() 实现业务级断言;
  • on_start 放登录逻辑,每虚拟用户执行一次;on_stop 做清理。

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. 为什么要在 client.get() 中使用 name 参数?

2. API 返回 HTTP 200 但业务码 code=5000,如何让 Locust 把它记为失败?

3. on_start 方法什么时候被调用?

4. 登录后要让该用户的所有后续请求自动携带 Authorization 头,最佳做法是?

🛠️ 动手实践

  1. 编写一个压测脚本:on_start 登录获取 token,后续请求携带 Authorization 头访问 /bearer
  2. https://httpbin.org/json 做 catch_response 校验:检查响应包含 "slideshow" 字段,否则标记 failure。
  3. name="/delay/[n]"/delay/1/delay/2/delay/3 三个请求归为一组,观察统计表的变化。

完成后进入第 4 章