lyyyuna 的小花园

动静中之动, by

RSS

k6 测试生命周期

发表于 2025-06

概览

在 k6 测试的生命周期中,脚本总是按照以下固定顺序依次执行各个阶段:

  1. init 上下文中的代码准备脚本、加载文件、导入模块并定义测试生命周期函数。必须阶段
  2. setup 函数执行阶段,设置测试环境并生成测试数据。可选阶段
  3. VU 代码执行阶段,在 defaultscenario 函数中运行,根据测试配置执行指定时长和次数。必选阶段
  4. teardown 函数执行阶段,进行数据后处理并关闭测试环境。可选阶段
// 1. init code

export function setup() {
  // 2. setup code
}

export default function (data) {
  // 3. VU code
}

export function teardown(data) {
  // 4. teardown code
}
测试阶段 目的 示例 调用规则
1. init 加载本地文件、导入模块、声明生命周期函数 打开 JSON 文件、导入模块 每个 VU 执行一次
2. Setup 准备测试数据,在 VU 间共享数据 调用 API 启动测试环境 整个测试执行一次
3. VU 代码 执行测试函数(通常为 default 函数) 发送 HTTPS 请求、验证响应 每次迭代执行一次,次数由测试配置决定
4. Teardown 处理 Setup 阶段的结果数据,停止测试环境 验证 Setup 结果、发送测试完成 Webhook 通知 整个测试执行一次

init 阶段

init 阶段是必选的。在测试运行前,k6 需要初始化测试环境。为准备测试,init 上下文中的代码会为每个 VU 执行一次。

init 阶段可能进行的操作包括:

所有未包含在生命周期函数内的代码都属于 init 上下文。init 上下文中的代码总是最先执行。

// init context: importing modules
import http from 'k6/http';
import { Trend } from 'k6/metrics';

// init context: define k6 options
export const options = {
  vus: 10,
  duration: '30s',
};

// init context: global variables
const customTrend = new Trend('oneCustomMetric');

// init context: define custom function
function myCustomFunction() {
  // ...
}

init 阶段 与 VU 阶段分离,可以避免在 VU 代码中执行无关的计算,从而提升 k6 的性能并确保测试结果更加可靠。不过,init 代码有一个限制:它不能发起 HTTP 请求。这一限制是为了确保 init 阶段在不同测试中具有可复现性(因为协议请求的响应是动态且不可预测的)。

VU 阶段

脚本必须至少包含一个场景函数(scenario function),用于定义虚拟用户(VU)的执行逻辑。该函数内部的代码即为 VU 代码。通常情况下,VU 代码位于 default 函数中,但也可以定义在场景配置指定的函数内(具体示例请参阅后续章节)。

export default function () {
  // do things here...
}

VU 代码会在整个测试期间循环执行。它可以发起 HTTP 请求、输出指标数据,基本上能完成负载测试所需的所有操作——唯独那些属于 init 上下文的操作除外。

具体限制包括:

这些功能必须由 init 代码来实现,而非 VU 代码。

默认函数的生命周期

  1. 顺序执行机制。VU 会从头到尾顺序执行 default() 函数。当执行到函数末尾时,会自动跳转回起始位置重新执行,形成循环测试。
  2. 重启重置机制。每次循环开始时,k6 会对 VU 执行重置操作:
    1. 自动清除所有 cookies
    2. 根据测试配置决定是否断开TCP连接(连接保持行为可通过测试参数配置)

setup 和 teardown 阶段

default 函数类似,setupteardown 也必须是导出函数。但与 default 函数不同的是,k6 在整个测试过程中只会调用 setupteardown 各一次。具体调用时机如下:

  1. setup:在测试开始时调用,位于 init 阶段之后、VU 阶段之前
  2. teardown:在测试结束时调用,位于 VU 阶段(即 default 函数执行)之后

init 阶段不同,在 setupteardown 阶段可以调用完整的 k6 API。例如,你可以执行以下操作:

import http from 'k6/http';

export function setup() {
  const res = http.get('https://quickpizza.grafana.com/api/json');
  return { data: res.json() };
}

export function teardown(data) {
  console.log(JSON.stringify(data));
}

export default function (data) {
  console.log(JSON.stringify(data));
}

忽略 setup 和 teardown 的执行

你可以通过命令行选项 --no-setup--no-teardown 来跳过 setupteardown 阶段的执行。

k6 run --no-setup --no-teardown ...

在 default 和 teardown 中使用 setup 中的数据

// 1. init code

export function setup() {
  // 2. setup code
}

export default function (data) {
  // 3. VU code
}

export function teardown(data) {
  // 4. teardown code
}

你可能已经注意到,default()teardown() 函数的签名都接收一个参数(本文中称为 data)。

以下示例演示了如何将数据从 setup 代码传递到 VU 和 teardown 阶段:

export function setup() {
  return { v: 1 };
}

export default function (data) {
  console.log(JSON.stringify(data));
}

export function teardown(data) {
  if (data.v != 1) {
    throw new Error('incorrect data: ' + JSON.stringify(data));
  }
}

例如,利用 setup() 函数返回的数据,你可以实现以下功能:

但需注意以下限制事项:

在分布式测试场景中,若要在所有虚拟用户(VU)之间传递可变数据并最终移交至 teardown 阶段,其实现将异常复杂且计算资源消耗巨大。这种设计会直接违背 k6 的核心设计原则:确保同一测试脚本可跨多种执行模式运行。

额外的生命周期

k6 还提供了其他几种使用生命周期函数的方式:

  1. handleSummary(),若需生成自定义测试报告,k6 会在测试完全结束时额外调用此生命周期函数。
  2. 场景函数 (Scenario Functions),除了默认的 default 函数外,你也可以在场景函数中运行 VU 代码。
import http from 'k6/http';
import { sleep } from 'k6';

export const options = {
  scenarios: {
    my_web_test: {
      // the function this scenario will execute
      exec: 'webtest',
      executor: 'constant-vus',
      vus: 50,
      duration: '1m',
    },
  },
};

export function webtest() {
  http.get('https://test.k6.io/contacts.php');
  sleep(Math.random() * 2);
}
lyyyuna 沪ICP备2025110782号-1