## 📌 核心知識
### 什麼是 k6?
* **開發者友善**:使用 **JavaScript (ES6)** 撰寫測試腳本。
* **高效能**:底層使用 **Go 語言** 打造,輕量且極省記憶體,單機就能模擬上萬個虛擬使用者 (VU)。
* **自動化整合**:非常適合直接嵌入 CI/CD Pipeline(如 GitHub Actions、GitLab CI)。
### k6 壓測的核心概念
* **VU (Virtual User)**:虛擬使用者。每一個 VU 都會獨立、重複執行腳本中的 `default` 函式。
* **Duration**:測試持續的時間。
* **Stages**:階段式壓測。用來模擬流量「逐步爬升 (Ramp-up) $\to$ 高峰維持 $\to$ 逐漸下降 (Ramp-down)」的真實情境。
* **Thresholds**:效能合格門檻。用來定義 API 的 SLO/SLA(例如:`95% 的請求必須在 200ms 內完成`),若未達標,k6 會判定測試失敗。
---
## 📝 範例說明
### 安裝 k6
你可以透過簡單的指令進行安裝:
* **Mac (Homebrew)**: `brew install k6`
* **Windows (Chocolatey)**: `choco install k6`
* **Linux (Debian/Ubuntu)**:
```bash
sudo gpg -k
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D661C44D
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install k6
```
### k6 REST API 通用模組範本
```javascript
import http from 'k6/http';
import { check, sleep } from 'k6';
// ==========================================
// 利用環境變數彈性調整 Options
// ==========================================
// 若外部沒有傳入變數,則使用 || 後面的「預設值」
const TARGET_VUS = parseInt(__ENV.VUS) || 25; // 預設 5 個 VU
const RAMP_TIME = __ENV.RAMP_TIME || '5m'; // 預設 爬升 10 秒
const RUN_TIME = __ENV.RUN_TIME || '30s'; // 預設 持續 30 秒
const MAX_LATENCY = parseInt(__ENV.MAX_LATENCY) || 500; // 預設 門檻 500ms
// ==========================================
// 定義兩種不同的 Options 配置
// ==========================================
// 方案 A: 使用 traditional stages (傳統階段式:控制人數 VU)
const stagesOptions = {
stages: [
{ duration: '10s', target: 10 }, // 10秒內爬升至 10 個 VU
{ duration: '30s', target: 10 }, // 維持 10 個 VU 測試 30 秒
{ duration: RAMP_TIME, target: TARGET_VUS }, // 動態設定持續壓測時間
{ duration: RAMP_TIME, target: TARGET_VUS }, // 動態設定持續壓測時間
],
thresholds: {
// 門檻數值也能從環境變數帶入
http_req_duration: [`p(95)<${MAX_LATENCY}`],
http_req_failed: ['rate<0.01'], // 期望失敗率低於 1%
},
};
// 方案 B: 使用 scenarios (固定 TPS:控制每秒請求數)
const scenariosOptions = {
scenarios: {
constant_tps_test: {
executor: 'constant-arrival-rate', // 固定到達率
rate: 15, // 目標每秒 15 次請求 (TPS = 15)
timeUnit: '1s', // 時間單位:1 秒
duration: '1m', // 測試持續 1 分鐘
preAllocatedVUs: 20, // 預先分配 20 個 VU
maxVUs: 100, // 應付遲鈍 API 的最高擴展上限
},
},
thresholds: {
http_req_duration: ['p(95)<500'],
},
};
// ==========================================
// 根據環境變數 TEST_MODE 動態切換 Options
// ==========================================
// 預設使用 'stages' 模式,除非外部傳入 TEST_MODE=tps
const mode = __ENV.TEST_MODE || 'stages';
export const options = (mode === 'tps') ? scenariosOptions : stagesOptions;
// ==========================================
// HTTP 工具類別(彈性 Header、Auth、Session)
// ==========================================
class HttpClient {
/**
* 建構子:初始化基礎設定
* @param {string} baseUrl - API 的基礎路徑 (例如: https://api.example.com)
* @param {string} authToken - 選擇性傳入 Bearer Auth Token
*/
constructor(baseUrl, authToken = null) {
this.baseUrl = baseUrl;
this.authToken = authToken;
this.cookieJar = http.cookieJar(); // 自動管理 Session (Cookies)
}
/**
* 內部方法:動態建立 Request Headers
* @param {object} customHeaders - 使用者自訂的額外 Headers
* @returns {object} 完整組合後的 Headers 物件
*/
_buildHeaders(customHeaders = {}) {
// 預設強制使用 JSON 格式
const defaultHeaders = {
'Content-Type': 'application/json',
'Accept': 'application/json',
};
// 如果建構子有帶入 Auth Token,自動加入 Authorization Header
if (this.authToken) {
defaultHeaders['Authorization'] = `Bearer ${this.authToken}`;
}
// 將自訂 Headers 與預設值合併(自訂值會覆蓋預設值)
return Object.assign({}, defaultHeaders, customHeaders);
}
/**
* 通用 GET 請求
* @param {string} endpoint - API 端點路徑
* @param {object} customHeaders - 彈性傳入的額外 Headers
*/
get(endpoint, customHeaders = {}) {
const url = `${this.baseUrl}${endpoint}`;
const params = {
headers: this._buildHeaders(customHeaders),
jar: this.cookieJar, // 帶入 Session Cookie
};
return http.get(url, params);
}
/**
* 通用 POST 請求
* @param {string} endpoint - API 端點路徑
* @param {object} payload - 要傳送的 JS 物件(內部會自動轉 JSON)
* @param {object} customHeaders - 彈性傳入的額外 Headers
*/
post(endpoint, payload = {}, customHeaders = {}) {
const url = `${this.baseUrl}${endpoint}`;
const params = {
headers: this._buildHeaders(customHeaders),
jar: this.cookieJar,
};
// 將 JS 物件轉為 JSON 字串傳送
return http.post(url, JSON.stringify(payload), params);
}
/**
* 通用 PUT 請求
* @param {string} endpoint - API 端點路徑
* @param {object} payload - 要更新的 JS 物件
* @param {object} customHeaders - 彈性傳入的額外 Headers
*/
put(endpoint, payload = {}, customHeaders = {}) {
const url = `${this.baseUrl}${endpoint}`;
const params = {
headers: this._buildHeaders(customHeaders),
jar: this.cookieJar,
};
return http.put(url, JSON.stringify(payload), params);
}
/**
* 通用 DELETE 請求
* @param {string} endpoint - API 端點路徑
* @param {object} customHeaders - 彈性傳入的額外 Headers
*/
delete(endpoint, customHeaders = {}) {
const url = `${this.baseUrl}${endpoint}`;
const params = {
headers: this._buildHeaders(customHeaders),
jar: this.cookieJar,
};
return http.del(url, null, params);
}
}
// ==========================================
// 測試情境主程式 (Default Function)
// ==========================================
export default function () {
// 示範使用免費的測試 API (JSONPlaceholder)
const baseUrl = __ENV.BASE_URL || 'https://jsonplaceholder.typicode.com';
// 假設已取得的 Bearer Token(範例)
const mockToken = __ENV.API_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
// 1. 初始化 Client(已內建 Token 與 Session 管理)
const client = new HttpClient(baseUrl, mockToken);
// ------------------------------------------
// 範例 A: GET 請求(帶入額外的彈性 Header)
// ------------------------------------------
const getRes = client.get('/posts/1', {
'X-Custom-Trace-Id': 'trace-123456', // 彈性擴充自訂 Header
});
// 驗證 GET 結果
check(getRes, {
'GET Status is 200': (r) => r.status === 200,
'GET Body has id': (r) => r.json().id === 1,
});
// ------------------------------------------
// 範例 B: POST 請求 (新增資料)
// ------------------------------------------
const newPostData = {
title: 'k6 Performance Testing',
body: 'Automated test using custom client class.',
userId: 1,
};
const postRes = client.post('/posts', newPostData);
// 驗證 POST 結果
check(postRes, {
'POST Status is 201': (r) => r.status === 201,
'POST Body contains title': (r) => r.json().title === newPostData.title,
});
// ------------------------------------------
// 範例 C: PUT 請求 (更新資料)
// ------------------------------------------
const updatePostData = {
id: 1,
title: 'Updated Title',
body: 'Updated Body Content',
userId: 1,
};
const putRes = client.put('/posts/1', updatePostData);
// 驗證 PUT 結果
check(putRes, {
'PUT Status is 200': (r) => r.status === 200,
});
// ------------------------------------------
// 範例 D: DELETE 請求 (刪除資料)
// ------------------------------------------
const deleteRes = client.delete('/posts/1');
// 驗證 DELETE 結果
check(deleteRes, {
'DELETE Status is 200': (r) => r.status === 200,
});
// 每個 VU 執行完一輪後暫停 1 秒,模擬真實使用者停頓
sleep(1);
}
```
### 執行指令範例
#### 🔹 情況 A:完全不給參數(直接使用預設值)
```bash
k6 run script.js
```
#### 🔹 情況 B:只修改人數與目標網址(針對 QA 環境)
```bash
k6 run -e BASE_URL=https://qa-api.example.com -e VUS=20 script.js
```
#### 🔹 情況 C:高強度壓測與調整檢驗標準(針對 Prod 環境)
```bash
k6 run -e BASE_URL=https://api.example.com \
-e VUS=100 \
-e RAMP_TIME=30s \
-e RUN_TIME=5m \
-e MAX_LATENCY=200 \
-e API_KEY=prod-secret-token \
script.js
```
### 看懂報告
執行結束後,k6 會印出強大的統計報告,重點欄位如下:
```text
✓ Status is 200
✓ Response time < 200ms
✓ http_req_duration..............: avg=120.5ms min=90ms med=110ms max=350ms p(95)=210ms
✓ http_req_failed................: 0.00% ✓ 0 ✗ 450
http_reqs......................: 450 11.25/s
vus............................: 1 min=1 max=20
```
#### 📊 關鍵指標說明:
* **`http_req_duration`**:API 響應時間(包含 `avg` 平均、`p(95)` 前 95% 人的回應時間)。
* **`http_req_failed`**:請求失敗率。
* **`http_reqs`**:總共發送了多少次 HTTP 請求以及 **RPS (每秒請求數 count/s)**。
* **`checks`**:斷言檢查點的成功率。
---
## 🔸 補充說明
* **k6 內建 CLI 旗標重寫 (CLI Flags Override)**:除了使用 `__ENV` 寫在 JavaScript 裡面外,k6 本身也支援直接透過 CLI 覆蓋 `options`。
例如:`k6 run --vus 50 --duration 2m script.js`。**CLI 旗標的優先權會高於腳本內部的 `options` 物件**。
* **讀取系統原生環境變數**:如果你的作業系統已經有 `export API_KEY=123`,在執行 k6 時只需要加上 `--export-ENV-vars` 或依據作業系統直接執行,k6 也能存取到該變數。
* **`http.CookieJar()`**:相當於瀏覽器的 Session 記憶體,會在該 VU 的請求生命週期中自動儲存並帶上伺服器回傳的 `Set-Cookie`。
* **`Object.assign()`**:用於動態合併物件。在此處是用來將「預設 Header」與「使用者自訂 Header」組合在一起,讓每一個請求都能彈性覆蓋或新增特定的 Header(如 `X-Request-ID` 等)。
# **VU(虛擬使用者數量)** 與 **TPS(每秒請求數/交易數)**
在 k6 中,`stages` 預設控制的是 **VU(虛擬使用者數量)**。
如果你想在不同的 stage 設定 **TPS(每秒請求數/交易數)**,就不能用預設的簡單模型,而是需要使用 k6 的 **Executor(執行器)**。
以下分別介紹 **「設定 VU Stages」** 與 **「設定 TPS Stages」** 的兩種寫法:
---
### 方法一:設定不同 Stage 的 VU(預設 Ramp-up / Ramp-down)
這是最常見的模式,適合用來測試「線上人數漸進式增加」時系統的承載力。
```javascript
import http from 'k6/http';
import { sleep } from 'k6';
export const options = {
stages: [
{ duration: '1m', target: 20 }, // 階段 1:1 分鐘內,VU 從 0 增加到 20 人 (Ramp-up)
{ duration: '3m', target: 20 }, // 階段 2:維持 20 人壓測 3 分鐘 (Plateau)
{ duration: '1m', target: 50 }, // 階段 3:1 分鐘內,VU 從 20 人拉高到 50 人
{ duration: '3m', target: 50 }, // 階段 4:維持 50 人壓測 3 分鐘
{ duration: '1m', target: 0 }, // 階段 5:1 分鐘內,VU 慢慢歸零 (Ramp-down)
],
};
export default function () {
http.get('https://test.k6.io');
sleep(1);
}
```
> **注意**:這種模式下,**TPS 是無法精確說明的**,因為 TPS 會隨著 API 的回應速度變化(API 回應越快,TPS 越高)。
---
### 方法二:設定不同 Stage 的 TPS(精確控制每秒吞吐量)
若你的情境是「我要模擬第 1 分鐘每秒 100 筆請求,第 2 分鐘拉高到每秒 500 筆請求」,你必須使用 `ramping-arrival-rate` 這個 Executor。
此模式下,**k6 會自動增減所需的 VU 數量**來強行達到你設定的 RPS/TPS 目標!
```javascript
import http from 'k6/http';
export const options = {
scenarios: {
my_tps_scenario: {
executor: 'ramping-arrival-rate', // 使用漸進式到達率執行器
startRate: 10, // 測試一開始的 TPS (10 req/s)
timeUnit: '1s', // 時間單位:秒 (即 TPS)
preAllocatedVUs: 50, // 預先分配的 VU 數 (節省資源)
maxVUs: 200, // 若 API 變慢,k6 最多可自動補充到 200 個 VU 來維持 TPS
stages: [
{ duration: '1m', target: 100 }, // 1 分鐘內,TPS 從 10 爬升到 100 TPS
{ duration: '3m', target: 100 }, // 維持 100 TPS 壓測 3 分鐘
{ duration: '1m', target: 500 }, // 1 分鐘內,TPS 從 100 暴增到 500 TPS
{ duration: '3m', target: 500 }, // 維持 500 TPS 壓測 3 分鐘
{ duration: '30s', target: 0 }, // 30 秒內降至 0 TPS
],
},
},
};
export default function () {
http.get('https://test.k6.io');
// 注意:在 constant/ramping-arrival-rate 模式下,預設函數內部不需要寫 sleep()
}
```
---
### 兩種模式怎麼選?
| 比較項目 | VU-based Stages (`stages`) | TPS-based Stages (`ramping-arrival-rate`) |
| --- | --- | --- |
| **控制核心** | **固定發起壓力的併發人數** | **固定每秒發送的請求總數** |
| **適用情境** | 評估系統能「同時服務多少在線使用者」 | 根據業務指標壓測(如:活動預計 1 秒進來 500 筆訂單) |
| **當系統變慢時** | TPS 會**自動下滑**(因為每個人都在卡住等待) | k6 會**自動啟動更多 VU** 強行發送請求,維持設定的 TPS |
| **潛在風險** | 無法保證精確的 TPS 量 | 若 `maxVUs` 設定太小或目標 TPS 太高,k6 會跳出 Warning 警告 VU 不夠用 |