菜单
文档breadcrumb arrow Grafana k6breadcrumb arrow 使用 k6breadcrumb arrow 测试生命周期
开源

测试生命周期

在 k6 测试的生命周期中,脚本总是按照以下阶段顺序运行:

  1. 处于 init 上下文中的代码准备脚本,加载文件、导入模块并定义测试生命周期函数必需
  2. setup 函数运行,设置测试环境并生成数据。可选。
  3. VU 代码在 default 或场景函数中运行,其运行时长和次数由 options 定义。必需
  4. teardown 函数运行,后处理数据并关闭测试环境。可选。

注意

生命周期函数

除了 init 代码外,每个阶段都发生在一个生命周期函数中,这些函数在 k6 运行时中按特定顺序调用。

JavaScript
// 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、setupteardown 阶段定义生命周期函数(以及自定义或 handleSummary() 函数)。

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

JavaScript
// 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 函数内,但也可以位于场景定义的函数内(示例见后续章节)。

JavaScript
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 函数类似,setupteardown 函数必须是导出函数。但与 default 函数不同的是,k6 在每次测试中只调用 setupteardown 函数一次。

  • setup 在测试开始时调用,在 init 阶段之后,VU 阶段之前。
  • teardown 在测试结束时调用,在 VU 阶段(default 函数)之后。

与 init 阶段不同,您可以在 setup 和 teardown 阶段调用完整的 k6 API。例如,您可以发起 HTTP 请求

JavaScript
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 阶段的执行。

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

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

再次回顾 k6 测试的基本结构

JavaScript
// 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 阶段:

JavaScript
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() 函数返回的任何数据的全新“副本”。

Diagram showing data getting returned by setup, then used (separately) by default and teardown functions

在所有 VU 之间传递可变数据,然后传递到 teardown,尤其是在分布式设置中,将极其复杂且计算密集。这违背了 k6 的一个核心目标:同一个脚本应该可以在多种模式下执行。

其他生命周期函数

k6 还有一些其他使用生命周期函数的方式:

  • handleSummary(). 如果您想自定义摘要,k6 会在测试结束时调用一个额外的生命周期函数。

    有关详细信息,请参阅自定义摘要

  • 场景函数。除了 default 函数外,您还可以在场景函数中运行 VU 代码。

    JavaScript
    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);
    }