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:请求超时时间(毫秒),默认 60sheaders:自定义请求头
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 使用率。
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()模拟用户思考时间,使压力更真实
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.}` },
});
}