﻿# 使用 Postman 進行 API 測試的幾個開發小技巧

我們現在經常在專案中大量的使用 [Postman](https://www.postman.com/) 進行 API 測試，不但 API 測試變的非常方便，搭配 [Postbot](https://blog.postman.com/introducing-postbot-postmans-new-ai-assistant/) 這套內建的 AI 助理，更能夠大幅提昇 API 測試的開發效率。這篇文章我就來分享幾個撰寫 API 測試的小技巧。

### 如何使用 Postman 進行 API 測試

我覺得大致上來說，使用 Postman 進行 API 測試的流程，可以分成以下幾個步驟：

1.  新增 HTTP 要求 (Add request)
2.  先確認可以順利按下 `Send` 發出 HTTP 要求
3.  切換至 `Tests` 頁籤撰寫測試案例 ( 使用 JavaScript 即可 )
4.  再次按下 `Send` 發出 HTTP 要求，並查看測試結果 ( `Test Results` )

圖示如下：

![Postman](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/31e45023d5d0befa482b-6d1a36c8-647b-4477-8e64-ef473caffa70.webp)

一個最簡單的例子如下：

```javascript
pm.test("response is ok", function () {
    pm.response.to.have.status(200);
});
```

你可以看到右上角有個 `Script with Postbot` 功能，按下去之後，輸入一些提示(Prompt)，例如 `Add tests to this request`，他就會全自動產生測試程式碼，這個功能非常方便，可以大幅提昇 API 測試的開發效率！

![Postbot](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/1c039092c11c8dd01ac6-d5aca8d2-ba09-4aa0-9220-f3aeceb90a4f.webp)

這裡需要特別注意的地方是，你一定要先確認可以順利按下 `Send` 發出 HTTP 要求，並且得到一些結果，然後才能透過 Postbot 幫你撰寫測試案例。

### 撰寫測試案例的基本結構

基本上 Postman 中的每一個 HTTP 要求，都可以撰寫一個以上的測試案例，每個測試案例的程式碼結構如下：

```javascript
pm.test("測試案例名稱", function () {
    // Assertions
});
```

你也可以用以下程式碼片段，在 `pm.test` 後面加上 `.skip` 即可讓這段測試案例**跳過執行**：

```javascript
pm.test.skip("測試案例名稱", () => {
    // Assertions
});
```

你也可以有條件的跳過特定幾個測試案例，例如：

```javascript
const shouldBeSkipped = true; // some condition

(shouldBeSkipped ? pm.test.skip : pm.test)("測試案例名稱", () => {
    // Assertions
});
```

### 各種 Assertions 撰寫範例

-   直接失敗的測試案例
    
    有時候我們後端開發人員尚未把 API 開發完成，或程式只有骨架而已，尚未實作任何商業邏輯，此時我們其實也可以先寫好一個會報「紅燈」的測試，也就是讓該 API 的測試案例直接失敗，此時你可以這樣寫測試案例程式：
    
    ```javascript
    pm.test('開發完成度', () => {
        pm.expect.fail('API 尚未開發完成');
    });
    ```
    
    事實上在執行時，會得到以下結果，訊息非常清楚：
    
    ```
    開發完成度 | AssertionError: API 尚未開發完成
    ```
    
-   測試 HTTP 狀態碼
    
    驗證 HTTP 狀態碼是否為 200
    
    ```javascript
    pm.response.to.have.status(200);
    ```
    
    你也可以這樣寫 (我個人比較喜歡這種統一的撰寫風格)
    
    ```javascript
    pm.expect(pm.response.code).to.equal(200);
    ```
    
    驗證 HTTP 狀態碼是否為 201 或 202
    
    ```javascript
    pm.expect(pm.response.code).to.be.oneOf([201,202]);
    ```
    
    驗證 HTTP 狀態碼的「文字」部分
    
    ```javascript
    pm.response.to.have.status("Created");
    ```
    
-   測試 HTTP 回應標頭 (Response Headers)
    
    驗證 HTTP 回應標頭包含了 `Cache-Control` 標頭
    
    ```javascript
    pm.response.to.have.header('Cache-Control');
    ```
    
    驗證 HTTP 回應標頭包含了 `Cache-Control` 標頭且內容等於 `no-cache`
    
    ```javascript
    pm.expect(pm.response.headers.get('Cache-Control')).to.eql('no-cache');
    ```
    
    驗證 HTTP 回應標頭包含了 `Content-Type` 標頭且內容等於 `application/json`
    
    ```javascript
    pm.expect(pm.response.headers.get('Content-Type')).to.eql('application/json');
    ```
    
    > 注意: `.to.eq()` 等同於用 `===` 做比較，這意味著 `{a: 1}` 不會等於 `{a: 1}` 的。而 `.to.eql()` 則是採用 deep equal 的方式做比較，可以比較兩個物件，這意味著 `{a: 1}` 將會等於 `{a: 1}` 喔！
    
-   測試 HTTP 回應 Cookies
    
    驗證 HTTP 回應標頭包含了 `.AspNet.ApplicationCookie` Cookie
    
    ```javascript
    pm.expect(pm.cookies.has('.AspNet.ApplicationCookie')).to.be.true;
    ```
    
    驗證 HTTP 回應標頭包含了 `isLoggedIn` Cookie 且內容等於 `1`
    
    ```javascript
    pm.expect(pm.cookies.get('isLoggedIn')).to.eql('1');
    ```
    
-   測試 HTTP 回應時間 (Response Time)
    
    驗證 HTTP 回應時間要小於 `1000ms`
    
    ```javascript
    pm.expect(pm.response.responseTime).to.be.below(1000);
    ```
    
-   測試 HTTP 回應內容 (Response Body)
    
    驗證 HTTP 回應內容**等於** `OK`
    
    ```javascript
    pm.response.to.have.body("OK");
    ```
    
    驗證 HTTP 回應內容**包含** `新增成功` (這種方式用於無法解析回應格式的狀況)
    
    ```javascript
    pm.expect(pm.response.text()).to.include('新增成功');
    ```
    
    驗證 HTTP 回應內容為 JSON 格式
    
    ```javascript
    pm.response.to.have.jsonBody();
    ```
    
-   測試 HTTP 回應內容 (Response Body) - 驗證 JSON 格式
    
    要對 API 回應的 JSON 內容做出斷言，首先要取得 JSON 物件！
    
    ```javascript
    const jsonData = pm.response.json();
    ```
    
    > 如果回應內容為 XML 的話，可以使用 `xml2Json` 內建函式將 XML 轉換為 JSON 物件：
    > 
    > ```javascript
    > const jsonData = xml2Json(responseBody);
    > ```
    
    驗證 JSON 結果一定是從一個「物件」開始：
    
    ```javascript
    pm.expect(jsonData).to.be.an('object');
    ```
    
    驗證 JSON 結果一定是從一個「陣列」開始：
    
    ```javascript
    pm.expect(jsonData).to.be.an('array');
    ```
    
    驗證 JSON 結果的各屬性型別：
    
    ```javascript
    pm.expect(jsonData.name).to.be.a("string");
    pm.expect(jsonData.age).to.be.a("number");
    pm.expect(jsonData.website).to.be.undefined;
    pm.expect(jsonData.email).to.be.null;
    ```
    
    > 注意: `to.be.a(type)` 或 `to.be.an(type)` 的第一個參數為型別名稱，例如: `string`、`number`、`boolean`、`object`、`array`、`function`、`null`、`undefined` 等等。
    
    驗證 JSON 結果的第 1 層的 `url` 屬性必須是個「字串」型別，且長度至少為 `1` 以上：
    
    ```javascript
    pm.expect(jsonData.url).to.be.a('string').and.to.have.lengthOf.at.least(1, "Value should not be empty");
    ```
    
    驗證 JSON 結果的第 1 層的 `type` 屬性必須是清單中的其中一個字串：
    
    ```javascript
    pm.expect(jsonData.type).to.be.oneOf(["Subscriber", "Customer", "User"]);
    ```
    
    驗證 JSON 結果的第一層必須包含特定屬性名稱：
    
    ```javascript
    pm.expect(jsonData.count).to.exist;
    pm.expect(jsonData.items).to.exist;
    ```
    
    驗證 JSON 結果的第 2 層的 `options` 屬性必須是個**物件**，且必須包含特定屬性名稱：
    
    ```javascript
    pm.expect(jsonData.options).to.exist.and.to.be.an('object');
    pm.expect(jsonData.options).to.have.property('foo1');
    pm.expect(jsonData.options).to.have.property('foo2');
    ```
    
    ```javascript
    pm.expect(jsonData.options).to.have.all.keys('foo1', 'foo2'); // 物件必須找到所有屬性 (完全比對)
    pm.expect(jsonData.options).to.have.any.keys('foo1', 'foo2'); // 物件必須找到任何屬性 (部分比對)
    ```
    

### 使用內建的動態變數 (Dynamic variables)

在 Postman 之中有一些內建的動態變數，可以讓你在測試案例中使用，例如使用 `$guid` 可以產生一個亂數的 GUID 來用。

不過在 `Tests` 頁籤中，必須透過 `pm.variables.replaceIn()` 函式來取得動態變數的值，例如：

```javascript
var guid = pm.variables.replaceIn('{{$guid}}');
```

> 完整的動態變數清單可參考官網的 [Dynamic variables](https://learning.postman.com/docs/writing-scripts/script-references/variables-list/) 文件。

### 相關連結

-   Postman Learning Center
    -   [Test script examples](https://learning.postman.com/docs/writing-scripts/script-references/test-examples/)
    -   [Dynamic variables](https://learning.postman.com/docs/writing-scripts/script-references/variables-list/)
    -   [Postman JavaScript reference](https://learning.postman.com/docs/writing-scripts/script-references/postman-sandbox-api-reference/#scripting-with-request-data)
-   [Postman Cheatsheet — Postman Quick Reference Guide Version 1.9.1 - January 2023 documentation](https://postman-quick-reference-guide.readthedocs.io/en/latest/cheatsheet.html#assertions)
-   [Snippets and Usage examples for Postman autotests](https://gist.github.com/AlexNDRmac/c5c69859d98f97b89746ca9c56dbf2ce)
-   [Expect / Should - Chai](https://www.chaijs.com/api/bdd/)
