3. k6 脚本基础:JavaScript 语法速览、模块系统与默认导出函数

k6 脚本本质上是 JavaScript (ES6+) 代码,运行在 k6 内置的 JavaScript 运行时中。虽然语法与标准 JavaScript 高度一致,但 k6 针对性能测试场景做了特定扩展和限制。掌握本节内容,你将能读懂并编写基础的 k6 压测脚本。

3.1 JavaScript 语法速览(k6 上下文)

k6 不支持 DOM、window 或 document 对象,但支持大部分 ES6 特性。以下是编写 k6 脚本时最常用的语法元素:

语法特性 示例 说明
变量声明 let vus = 10;
const BASE_URL = "http://test.k6.io";
优先使用 const,避免 var
箭头函数 params => { return { headers }; } 简洁回调,常用于 check()
模板字符串 `${BASE_URL}/api/users/${id}` 拼接 URL 和动态参数
解构赋值 const { status, body } = http.get(url); 快速提取响应属性
展开运算符 const newParams = {...defaultParams, timeout: 30}; 合并配置对象
数组方法 data.map(item => item.id).filter(id => id > 0); 处理 CSV/JSON 数据
💡 提示: k6 脚本中 不要使用 setTimeout、setInterval、Promise 或 async/await。k6 使用同步阻塞模型来保证压力可控,异步操作会导致结果不可预测。

3.2 k6 模块系统

k6 通过内置模块提供 HTTP 请求、指标收集、加密等功能。模块通过 import 语句引入,所有模块都是 k6 运行时原生支持的,无需安装 npm 包。

3.2.1 k6/http — HTTP 请求核心

最常用的模块,提供 get()、post()、put()、del() 等方法。每个请求返回一个 HttpResponse 对象,包含 status、body、headers、timings 等属性。

import http from 'k6/http';

export default function () {
  // GET 请求
  let res = http.get('https://test.k6.io');

  // POST 请求,带 JSON body
  let payload = JSON.stringify({ username: 'admin', password: '123456' });
  let params = {
    headers: { 'Content-Type': 'application/json' },
    tags: { name: 'login' },  // 自定义标签,用于结果过滤
  };
  let postRes = http.post('https://test.k6.io/api/login', payload, params);

  // 检查响应状态
  check(postRes, {
    'login status is 200': (r) => r.status === 200,
  });
}
📌 关键参数:
  • tags:给请求打标签,在结果分析中按标签分组
  • timeout:请求超时时间(毫秒),默认 60s
  • headers:自定义请求头

3.2.2 k6/metrics — 自定义指标

当内置指标(如 http_req_duration)不够用时,可以创建自定义指标来监控业务逻辑。支持四种指标类型:

类型 构造函数 适用场景 聚合方式
Counter new Counter('name') 计数,如失败次数、重试次数 求和
Gauge new Gauge('name') 瞬时值,如当前连接数、队列深度 最后值 / 最小 / 最大
Rate new Rate('name') 比率,如通过率、错误率 百分比(通过/总数)
Trend new Trend('name') 分布,如响应时间、数据包大小 平均 / 中位数 / p95 / p99
import { Counter, Trend } from 'k6/metrics';

// 定义自定义指标
let businessErrors = new Counter('business_errors');
let orderDuration = new Trend('order_duration');

export default function () {
  let start = Date.now();

  // 模拟业务操作
  let res = http.post('https://api.example.com/order');
  if (res.status !== 200) {
    businessErrors.add(1);  // 记录业务错误
  }

  orderDuration.add(Date.now() - start);  // 记录耗时分布
}

3.2.3 k6/crypto — 加密与签名

在压测需要签名或加密的 API 时,k6/crypto 模块提供 HMAC、哈希、Base64 等能力,无需外部库。

import crypto from 'k6/crypto';
import encoding from 'k6/encoding';

export default function () {
  // HMAC-SHA256 签名
  let hmac = crypto.createHMAC('sha256', 'my-secret-key');
  hmac.update('message-to-sign');
  let signature = hmac.digest('hex');

  // MD5 哈希
  let hash = crypto.md5('data', 'hex');

  // Base64 编码
  let encoded = encoding.b64encode('hello');
  let decoded = encoding.b64decode(encoded);

  // 在请求头中使用签名
  let params = {
    headers: {
      'X-Signature': signature,
      'Authorization': `Bearer ${encoded}`,
    },
  };
  http.get('https://api.example.com/secure', params);
}
⚠️ 注意: k6/crypto 中的操作是同步的,不会阻塞事件循环。但大量加密操作会消耗 CPU,在容量压测时需关注 k6 实例的 CPU 使用率。
k6 模块系统架构 k6 Runtime k6/http HTTP 请求 / 响应 k6/metrics 自定义指标 k6/crypto 加密 / 签名 k6/encoding k6/execution k6/ws 所有模块通过 import 语句引入,无需 npm 安装,k6 运行时原生支持 模块之间可组合使用,例如用 k6/crypto 生成签名后通过 k6/http 发送请求

3.3 默认导出函数(export default function)

k6 脚本的核心入口是 默认导出函数。每个 VU(虚拟用户)会反复执行这个函数,直到测试结束。函数内部的代码就是每个用户每次迭代要执行的操作。

import http from 'k6/http';
import { check, sleep } from 'k6';

// 默认导出函数:每个 VU 每次迭代执行一次
export default function () {
  // 1. 发送请求
  let res = http.get('https://test.k6.io');

  // 2. 检查结果
  check(res, {
    'status is 200': (r) => r.status === 200,
    'response time < 500ms': (r) => r.timings.duration < 500,
  });

  // 3. 思考时间(模拟用户等待)
  sleep(1);
}

3.3.1 执行模型

概念 说明 配置方式
VU(虚拟用户) 并发执行 default function 的独立 goroutine --vus 10 或 options.vus
迭代(iteration) 每个 VU 执行一次 default function 称为一次迭代 --iterations 100 或 options.iterations
持续时间 测试运行的总时长 --duration 30s 或 options.duration

3.3.2 生命周期与辅助函数

除了 default function,k6 还支持 setup() 和 teardown() 函数,用于测试前后的准备和清理工作。

import http from 'k6/http';
import { sleep } from 'k6';

// setup:测试开始前执行一次(仅一次)
export function setup() {
  let res = http.post('https://api.example.com/login', {
    username: 'admin',
    password: 'secret',
  });
  return { : res.json(). };  // 返回数据传递给 default function
}

// default function:每个 VU 每次迭代执行
export default function (data) {
  // data 来自 setup 的返回值
  let params = {
    headers: { 'Authorization': `Bearer ${data.}` },
  };
  http.get('https://api.example.com/dashboard', params);
  sleep(1);
}

// teardown:测试结束后执行一次(仅一次)
export function teardown(data) {
  http.post('https://api.example.com/logout', {}, {
    headers: { 'Authorization': `Bearer ${data.}` },
  });
}
📌 最佳实践:
  • setup() 中只做一次性的准备工作(如登录获取 )
  • default function 保持轻量,只包含被测业务逻辑
  • 不要在 default function 中做数据初始化,避免影响压测结果
  • 使用 sleep() 模拟用户思考时间,使压力更真实
k6 脚本执行生命周期 setup() 执行 1 次(初始化) default function() 每个 VU 反复执行 teardown() 执行 1 次(清理) setup() 的返回值通过参数传递给 default function 和 teardown() 每个 VU 独立执行 default function,但共享 setup() 返回的数据 执行时间线 setup() default function(多次迭代) teardown()

3.4 完整脚本示例

综合以上知识点,一个完整的 k6 压测脚本通常包含:模块导入、配置选项、setup、default function 和 teardown。

import http from 'k6/http';
import { check, sleep } from 'k6';
import { Counter, Trend } from 'k6/metrics';
import crypto from 'k6/crypto';

// 自定义指标
let errorCount = new Counter('custom_errors');
let apiLatency = new Trend('api_latency');

// 测试配置
export let options = {
  vus: 10,
  duration: '30s',
  thresholds: {
    http_req_duration: ['p(95)<2000'],  // 95% 请求在 2s 内
    custom_errors: ['count<100'],        // 错误总数小于 100
  },
};

// 初始化:获取 
export function setup() {
  let res = http.post('https://api.example.com/auth', {
    username: 'loadtest',
    password: 'k6rocks',
  });
  return { : res.json().access_ };
}

// 主测试逻辑
export default function (data) {
  let timestamp = Date.now().toString();
  let hmac = crypto.createHMAC('sha256', data.);
  hmac.update(timestamp);
  let signature = hmac.digest('hex');

  let params = {
    headers: {
      'Authorization': `Bearer ${data.}`,
      'X-Timestamp': timestamp,
      'X-Signature': signature,
    },
  };

  let start = Date.now();
  let res = http.get('https://api.example.com/orders', params);
  apiLatency.add(Date.now() - start);

  if (res.status !== 200) {
    errorCount.add(1);
    console.error(`Request failed: ${res.status} ${res.body}`);
  }

  check(res, {
    'status is 200': (r) => r.status === 200,
    'has orders': (r) => r.json().length > 0,
  });

  sleep(1);
}

// 清理
export function teardown(data) {
  http.post('https://api.example.com/logout', {}, {
    headers: { 'Authorization': `Bearer ${data.}` },
  });
}