前言

POSTMAN 通常是目前在做 API 層級的測試時的首選,不過通常 API 的測試不會只有 End-to-End 的測試就結束了,有些較完整的還會包含負載測試(Load Test),不過若只有用 POSTMAN 就想要完成 Load test 那就辛苦了。

POSTMAN Test Script

這邊我們就使用 POSTMAN 自帶的 Collection (Postman Echo) 來做實驗,這邊簡單起見就只抓一個 Get method 來做。匯出的檔案大概會長以下這樣:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
{
    "info": {
        "_postman_id": "{_postman_id}",
        "name": "{your_collection_name}",
        "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
    },
    "item": [
        {
            "name": "{your_request_name}",
            "event": [
                {
                    "listen": "test",
                    "script": {
                        "id": "e6b1db6b-fac1-4f5c-820e-d0a6806b1a1e",
                        "exec": [
                            "tests[\"Body contains headers\"] = responseBody.has(\"headers\");",
                            "tests[\"Body contains args\"] = responseBody.has(\"args\");",
                            "tests[\"Body contains url\"] = responseBody.has(\"url\");",
                            "",
                            "var responseJSON;",
                            "",
                            "try { responseJSON = JSON.parse(responseBody); }",
                            "catch (e) { }",
                            "",
                            "",
                            "tests[\"Args key contains argument passed as url parameter\"] = 'test' in responseJSON.args"
                        ],
                        "type": "text/javascript"
                    }
                }
            ],
            "request": {
                "method": "GET",
                "header": [],
                "url": {
                    "raw": "https://postman-echo.com/get?test=123",
                    "protocol": "https",
                    "host": [
                        "postman-echo",
                        "com"
                    ],
                    "path": [
                        "get"
                    ],
                    "query": [
                        {
                            "key": "test",
                            "value": "123"
                        }
                    ]
                },
                "description": "The HTTP `GET` request method is meant to retrieve data from a server. The data\nis identified by a unique URI (Uniform Resource Identifier). \n\nA `GET` request can pass parameters to the server using \"Query String \nParameters\". For example, in the following request,\n\n> http://example.com/hi/there?hand=wave\n\nThe parameter \"hand\" has the value \"wave\".\n\nThis endpoint echoes the HTTP headers, request parameters and the complete\nURI requested."
            },
            "response": []
        }
    ],
    "protocolProfileBehavior": {}
}

這個方式的缺點就是 Postman 匯出的最小單位是 Collection ,所以很可能實際運用時你會需要把本機測試用的 Collection 與壓力測試用的分成兩個 Collection。

What is K6

過往做負載測試很多都會用 JMeter 來做,JMeter 教學資源很多同時可以自行開發外掛以符合需求,也可以使用 GUI 進行設定,最大的缺點大概就是整體運行偏笨重相依 JVM,而且測試腳本的分享比較不好進行版控(內容比對不容易看出來到底改了什麼),改用 POSTMAN 來進行 API 測試設定快速又好上手,畢竟使用 JMeter 來測試還是比較 heavy 一點。不過使用 POSTMAN 來測試會遇到一個問題,那就是壓力測試 POSTMAN 本身並不支援,為了讓測試的體驗可以更完整,讓壓測可以利用 POSTMAN 完成我們可以借助 K6 這套工具來達成這個效果,讓我們可以把 POSTMAN 所存的 API request 都抓來壓一壓。

Install K6

官方教學

其實若是 Windows 使用者的話可以直接下載。若是不想處理環境等相關問題的話,K6 本身有提供 Docker 版本可以下載,雖然在執行時會需要啟動 Docker 但是可以確保執行環境的一致性也是個好處。 值得一提的是 K6 雖然是執行 javascript 但是他並不需要 NodeJS,他是用 Golang 寫的,只是附帶一個 javascript 的執行環境。

Convert to K6

雖然 K6 可以做壓測但他本身並不直接支援 POSTMAN 所匯出的檔案,我們會需要一套轉換工具將 POSTMAN 匯出的 json 檔案轉換成 K6 可以執行的 js 檔案。要做到這個我們可以利用 NodeJS 的一個套件 postman-to-k6 來完成,它能將 POSTMAN 匯出的 json 檔案轉換成 K6 可以理解的形式,包含 PreRequest scripts / Test scripts / variables 等。但是有些行為是不支援的,需要改成 K6 的寫法,無法支援的功能請參考這裡

讓我們條列一下步驟

  1. Install NodeJS

    ref: https://nodejs.org/en/

  2. Install postman-to-k6 module

    npm install -g postman-to-k6

  3. Export POSTMAN collection

    就是單純的把 POSTMAN 裡要做壓測的 Collection 給 export 出成 json 檔案。

  4. Convert your POSTMAN collection to K6 script

    postman-to-k6 postman_collection_export.json -o k6-script.js

轉換完畢應該至少會有兩個檔案,一個 JS 檔就是要 K6 執行的 script,另一個就是他會用到的 library 檔。

就這樣!簡單的四個步驟就完成執行壓測的前置作業了!

產出的檔案大概會類似這樣

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Auto-generated by the Load Impact converter

import "./libs/shim/core.js";

export let options = { maxRedirects: 4 };

const Request = Symbol.for("request");
postman[Symbol.for("initial")]({
  options
});

export default function() {
  postman[Request]({
    name: "GET Request Copy",
    id: "7af25195-60ed-4c1b-b372-1469ea7112c7",
    method: "GET",
    address: "https://postman-echo.com/get?test=123",
    post(response) {
      tests["Body contains headers"] = responseBody.has("headers");
      tests["Body contains args"] = responseBody.has("args");
      tests["Body contains url"] = responseBody.has("url");

      var responseJSON;

      try {
        responseJSON = JSON.parse(responseBody);
      } catch (e) {}

      tests["Args key contains argument passed as url parameter"] =
        "test" in responseJSON.args;
    }
  });
}

若你跟我一樣抓 POSTMAN Echo 來實驗的話,直接把轉譯過的 script 跑 K6 那肯定會遇到問題,我們在後面一點來解題。

Setup K6 Parameters

K6 本身提供了很多參數來作為執行時對測試的控制,而最直覺也是最重要的參數莫過於 VUs (virtual users)了,這個就是指明你要模擬多少使用者來打這個 API。不過我個人比較常用的是另外幾個設定,Stages / Thresholds

Stages 相較於設定 VUs 讓 K6 自己決定人數的變化,使用這個可以控制更多細節,以下是個簡單的範例:

1
2
3
4
5
6
stages: [
    { duration: "1m", target: 60},
    { duration: "1m", target: 80},
    { duration: "1m", target: 60},
    { duration: "1m", target: 0}
]

這邊就是全程跑 4分鐘,然後每一分鐘的 VUs 會到多少,這也是目前我個人比較常用的壓測方式。

而另一個 Thresholds 就是判定怎樣的 request 算是成功,這邊的設定與 POSTMAN 的 test script 所偏向的商業邏輯面比較不同,這邊所設定的閥值通常比較像是效能有關的,可以參考官方的說明:Thresholds

1
2
3
thresholds: {
    http_req_duration: [{ threshold: 'p(95)<100', abortOnFail: false, delayAbortEval: '1m'}],
}

這個範例就是我指明 request 的全程花費時間的 P 值在信心度 95 的狀況下要有 100ms 就回應我的效能,否則算失敗,且就算失敗也不要停止。而以下就是試跑的結果:

threshold failed

Execute K6 and Export Result

最簡單又直接的執行指令就是

k6 run k6-script.js

而輸出的話預設就會是在 console 畫面中印出來。也可以把一些 options 當作參數傳入,但我個人偏好把這些 options 寫在檔案裏面。

若你是照搬我這邊的範例的話需要改寫一下 POSTMAN 的 test script 才不會出錯。

首先要在 k6 script 引入

import "./libs/shim/expect.js";

這個套件,我們的測試語法會需要用到它。 而 default function 裡的 post 就改成

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// post 這邊傳入的參數其實就是 POSTMAN 打過去的 request 以及相關的數值,涵蓋回應時間等資料
post(pmRequest) {
    pm.test("Body contains headers", function(){
        var jsonData = pm.response.json();
        pm.expect(jsonData).to.have.property("headers");
    });
    pm.test("Body contains args", function(){
        var jsonData = pm.response.json();
        pm.expect(jsonData).to.have.property("args");
    });
    pm.test("Body contains url", function(){
        var jsonData = pm.response.json();
        pm.expect(jsonData).to.have.property("url");
    });
    pm.test("Args key contains argument passed as url parameter", function(){
        var jsonData = pm.response.json();
        pm.expect(jsonData.args).to.have.property("test")
    });
}

Conclusion

POSTMAN 在這短短幾年內可以說是席捲了在 API 開發與測試的一套工具,他的簡單易用以及 script 方便納入版控的特性深獲眾多開發者的心,但他目前針對壓測等較為 heavy 的測試的支援是較為不足的,還好有很多好用的工具可以跟他搭配,讓 POSTMAN 與這些測試的相容更為容易,也有越來越多的 QA/QE 在使用這套工具,可以直接把 RD 在開發時的一些 test script 稍加調整後就能併入整合測試/壓力測試中,這也讓開發團隊內部之間的技術可以直接分享。我想未來 POSTMAN 在開發與測試 API 這塊應該會持續佔有領導地位吧~

Reference