## 📌 核心知識
在 k6 中測試 RESTful API 時,通常會需要統一處理 API 的 Request Headers(如 `Content-Type: application/json`)、認證資訊與 Cookie。將這些邏輯封裝成通用函式,可以讓情境腳本(Scenario)變得非常簡潔且容易維護。
---
## 📝 範例說明
### k6 REST API 通用模組範本
```javascript
import http from 'k6/http';
import { check, sleep } from 'k6';
// ==========================================
// 1. 壓測環境與階段設定 (Options)
// ==========================================
export const options = {
stages: [
{ duration: '10s', target: 5 }, // 10秒內逐漸增加至 5 個虛擬使用者 (VU)
{ duration: '20s', target: 5 }, // 維持 5 個 VU 壓測 20 秒
{ duration: '5s', target: 0 }, // 5秒內緩降至 0 個 VU
],
thresholds: {
http_req_duration: ['p(95)<500'], // 期望 95% 的請求都在 500ms 內完成
http_req_failed: ['rate<0.01'], // 期望失敗率低於 1%
},
};
// ==========================================
// 2. 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);
}
}
// ==========================================
// 3. 測試情境主程式 (Default Function)
// ==========================================
export default function () {
// 示範使用免費的測試 API (JSONPlaceholder)
const baseUrl = 'https://jsonplaceholder.typicode.com';
// 假設已取得的 Bearer Token(範例)
const mockToken = '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);
}
```
---
## 🔸 類似概念
* **`http.CookieJar()`**:相當於瀏覽器的 Session 記憶體,會在該 VU 的請求生命週期中自動儲存並帶上伺服器回傳的 `Set-Cookie`。
* **`Object.assign()`**:用於動態合併物件。在此處是用來將「預設 Header」與「使用者自訂 Header」組合在一起,讓每一個請求都能彈性覆蓋或新增特定的 Header(如 `X-Request-ID` 等)。