测试生命周期
在 k6 测试的生命周期中,脚本总是按照以下阶段顺序运行:
- 处于
init
上下文中的代码准备脚本,加载文件、导入模块并定义测试生命周期函数。必需。 setup
函数运行,设置测试环境并生成数据。可选。- VU 代码在
default
或场景函数中运行,其运行时长和次数由options
定义。必需。 teardown
函数运行,后处理数据并关闭测试环境。可选。
注意
生命周期函数
除了 init 代码外,每个阶段都发生在一个生命周期函数中,这些函数在 k6 运行时中按特定顺序调用。
// 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 代码可能会被更频繁地调用。
** 如果 Setup
函数异常结束(例如抛出错误),则不会调用 teardown()
函数。请考虑在 setup()
函数中添加逻辑以处理错误并确保正确清理。
init 阶段
init 阶段是必需的。在测试运行之前,k6 需要初始化测试条件。为了准备测试,位于 init
上下文中的代码会在每个 VU 中运行一次。
一些可能在 init
中发生的操作包括以下内容:
- 导入模块
- 从本地文件系统加载文件
- 为所有
options
配置测试 - 为 VU、
setup
和teardown
阶段定义生命周期函数(以及自定义或handleSummary()
函数)。
所有不在生命周期函数中的代码都属于 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 阶段
脚本必须至少包含一个定义 VU 逻辑的场景函数。此函数内部的代码是 VU 代码。通常,VU 代码位于 default
函数内,但也可以位于场景定义的函数内(示例见后续章节)。
export default function () {
// do things here...
}
VU 代码在整个测试持续时间内会反复运行。 VU 代码可以发起 HTTP 请求、发出指标,并通常执行负载测试应完成的一切任务。唯一的例外是 init
上下文中的作业。
- VU 代码不会从本地文件系统加载文件。
- VU 代码不会导入任何其他模块。
同样,这些任务由 init 代码执行,而不是 VU 代码。
default 函数生命周期
VU 按顺序从头到尾执行 default()
函数。一旦 VU 执行到函数末尾,它会循环回到开头并重新执行所有代码。
作为此“重启”过程的一部分,k6 会重置 VU。Cookie 会被清除,并且 TCP 连接可能会断开(取决于您的测试配置选项)。
Setup 和 Teardown 阶段
与 default
函数类似,setup
和 teardown
函数必须是导出函数。但与 default
函数不同的是,k6 在每次测试中只调用 setup
和 teardown
函数一次。
setup
在测试开始时调用,在 init 阶段之后,VU 阶段之前。teardown
在测试结束时调用,在 VU 阶段(default
函数)之后。
与 init 阶段不同,您可以在 setup 和 teardown 阶段调用完整的 k6 API。例如,您可以发起 HTTP 请求
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
跳过 setup 和 teardown 阶段的执行。
k6 run --no-setup --no-teardown ...
在 default 和 teardown 中使用来自 setup 的数据
再次回顾 k6 测试的基本结构
// 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
代码中后处理数据
但是,有一些限制。
- 您只能在
setup
和其他阶段之间传递数据(即 JSON)。不能传递函数。 - 如果
setup()
函数返回的数据很大,它将消耗更多内存。 - 您不能在
default()
函数中操作数据,然后将其传递给teardown()
函数。
最好认为每个阶段和每个 VU 都可以访问 setup()
函数返回的任何数据的全新“副本”。
在所有 VU 之间传递可变数据,然后传递到 teardown,尤其是在分布式设置中,将极其复杂且计算密集。这违背了 k6 的一个核心目标:同一个脚本应该可以在多种模式下执行。
其他生命周期函数
k6 还有一些其他使用生命周期函数的方式:
handleSummary()
. 如果您想自定义摘要,k6 会在测试结束时调用一个额外的生命周期函数。有关详细信息,请参阅自定义摘要。
场景函数。除了
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); }