Skip to content

第 7 章 · TaskSet 与嵌套任务

本章目标:掌握 TaskSet 类的编写方式,实现任务的分层组织与嵌套调度,学会用 interrupt() 跳出子任务集,理解 on_start / on_stop 生命周期方法。

7.1 为什么需要 TaskSet

当用户行为模型变得复杂时(如"先登录 → 浏览商品 → 加入购物车 → 结算"),把所有 @task 堆在一个 HttpUser 类里会让代码难以维护。TaskSet 允许你将任务按业务场景分组:

python
from locust import HttpUser, TaskSet, task, between

class BrowsingBehavior(TaskSet):
    """模拟浏览商品的用户行为"""

    @task(5)
    def view_products(self):
        self.client.get("/api/products")

    @task(3)
    def view_product_detail(self):
        self.client.get("/api/products/1")

    @task(1)
    def stop_browsing(self):
        # 离开浏览模式
        self.interrupt()


class ShoppingUser(HttpUser):
    wait_time = between(1, 3)
    host = "https://shop.example.com"
    tasks = {BrowsingBehavior: 10}  # 权重为 10

HttpUser 通过 tasks 属性引用一个或多个 TaskSet。当 TaskSet 被选中后,其中的任务开始循环执行,直到调用 interrupt() 返回上层。

7.2 嵌套 TaskSet

TaskSet 可以多层嵌套,构建复杂的用户旅程模型:

python
class LoginTask(TaskSet):
    @task
    def login(self):
        resp = self.client.post("/api/login", json={
            "username": "test_user",
            "password": "secret"
        })
        if resp.status_code == 200:
            # 登录成功,进入购物流程
            self.interrupt()  # 返回父级


class ShoppingTask(TaskSet):
    tasks = {
        BrowseProducts: 5,
        AddToCart: 3,
        Checkout: 1
    }

    @task
    def stay_here(self):
        pass  # 保持当前层级的兜底任务


class EcommerceUser(HttpUser):
    wait_time = between(1, 3)
    host = "https://shop.example.com"

    tasks = [LoginTask]

每层 TaskSet 都有自己的任务权重系统。interrupt(reschedule=True) 可以在跳出后立即重新选择下一个任务。

7.3 interrupt() 的用法

interrupt() 是从子 TaskSet 返回到父级的关键方法。不调用它的话,agent 将永远留在当前 TaskSet 中循环。

python
class SessionTimeout(TaskSet):
    @task
    def check_session(self):
        resp = self.client.get("/api/session/check")
        if resp.json().get("expired"):
            self.interrupt(reschedule=True)  # 会话过期 → 返回上级重新登录

    @task
    def do_work(self):
        self.client.get("/api/work")
python
# interrupt() 参数说明
self.interrupt()               # 跳出后等待下一轮再选任务
self.interrupt(reschedule=True)  # 跳出后立即重新调度

忘记 interrupt()

最常见的错误是在子 TaskSet 中忘记设置退出条件,导致所有虚拟用户永远卡在该层级。确保每个子 TaskSet 都有至少一个能触发 interrupt() 的路径。

7.4 on_start 和 on_stop 生命周期

每个 TaskSet(和 User)都支持两个生命周期方法:

  • on_start:虚拟用户启动时执行一次(如登录、获取 token)
  • on_stop:虚拟用户停止时执行一次(如登出、清理资源)
python
class AuthenticatedUser(HttpUser):
    host = "https://api.example.com"
    wait_time = between(1, 3)

    def on_start(self):
        """每个虚拟用户启动时执行一次"""
        resp = self.client.post("/api/auth/login", json={
            "username": f"user_{id(self)}",
            "password": "test123"
        })
        self.token = resp.json()["token"]
        self.headers = {"Authorization": f"Bearer {self.token}"}

    def on_stop(self):
        """虚拟用户停止时执行清理"""
        self.client.post(
            "/api/auth/logout",
            headers=self.headers
        )

    class BrowseTask(TaskSet):
        @task
        def get_data(self):
            # 使用父类的 token
            self.client.get(
                "/api/data",
                headers=self.parent.headers  # 访问父级属性
            )

访问父级

在嵌套 TaskSet 内部通过 self.parent 访问父级 User 或 TaskSet 实例的属性和方法。

7.5 SequentialTaskSet:顺序执行

默认情况下 TaskSet 中的任务按权重随机选取。如果你需要严格按顺序执行(如注册→验证→完成),使用 SequentialTaskSet

python
from locust import SequentialTaskSet, HttpUser, task

class RegistrationFlow(SequentialTaskSet):
    """严格按照顺序执行的注册流程"""

    @task
    def step1_submit_form(self):
        self.client.post("/api/register", json={"email": "new@test.com"})
        # 保存返回值供下一步使用
        self.user_id = self.client.get("/api/me").json()["id"]

    @task
    def step2_verify_email(self):
        self.client.post("/api/verify", json={"user_id": self.user_id})

    @task
    def step3_complete_profile(self):
        self.client.put(f"/api/users/{self.user_id}", json={
            "nickname": "新用户"
        })

class SignupUser(HttpUser):
    host = "https://app.example.com"
    tasks = [RegistrationFlow]

SequentialTaskSet 执行完最后一步后会自动回到第一步继续循环,除非你调用 interrupt()

本章小结

  • TaskSet 按业务场景分组任务,通过父类 tasks 属性引用;
  • 多层嵌套构建复杂用户旅程,interrupt() 跳出子层级;
  • on_start / on_stop 处理一次性初始化和清理;
  • SequentialTaskSet 保证任务按声明顺序执行;
  • 子 TaskSet 通过 self.parent 访问父级属性。

🧪 随堂测验

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

1. 从子 TaskSet 返回到父级的方法是?

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

3. SequentialTaskSet 与普通 TaskSet 的核心区别是?

4. 在嵌套 TaskSet 中如何访问顶层 User 的属性?

🛠️ 动手实践

  1. 设计三层嵌套 TaskSet:登录 → 浏览 → 下单,每层有独立的权重配置。
  2. 在子 TaskSet 中使用 interrupt() 实现"会话超时自动重新登录"逻辑。
  3. SequentialTaskSet 编写完整的"密码找回"压测流程(请求重置→验证码校验→设置新密码)。