K6 壓力測試

Posted by Adam on August 24, 2022
## 📌 核心知識 在 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` 等)。