今年是 2022 年,我加入了一間提供 API 服務為主的公司,有很多基礎建設都還沒有存在,這也意味著有很多東西是需要溝通與建構的,其中就包含了將 API 測試案例自動化的基本建置,於是就產生了這篇紀錄,如果不想看心路歷程只想看 code 的話可以直接到 Github 上參考。
https://github.com/SQZ777/jest-for-api-automation-template.git
各個 subtitle 如下
- 為何不使用 Postman 來做 API Automation
- 語言選擇
- 測試框架選擇
- Modules 的分層
- 各個 Modules 的介紹與實作
- 結語和本篇沒提到的事
首先在使用程式碼撰寫 API 自動化的測試案例時,一定會先遇到這個問題
為何不使用 Postman 來做 API Automation?
所以就先來列一下 Postman 的優/缺點吧
優點如下
- UI 介面容易使用,門檻極低
- 可以透過 JavaScript 來實作檢查 response 的 script
- 各個平台(Mac, Windows)都可以使用
- 可以透過 newman(CLI)來建置 CI 的流程
接著列一下使用 Postman 執行管理自動化案例會遇到的問題
- 大量自動化案例會產生極大的維護成本
- 無法重用的 test script
- 受測 API 如果新增 required fields,會需要手動更新大量既有的 script
- 執行 collection runner 再透過 csv 檔案來匯入測試資料的過程過於繁瑣
Postman 在實作小型的 API 驗證時,是一個非常好用的工具,但需要實作大量 API 測試案例時,在 Postman 上管理這些案例會產生很大的維護成本,不信的話你可以試試,所以我們需要透過撰寫程式的方式來管理這些即將被實作的 API 測試案例。
語言選擇
首先要選擇的是語言,依照公司內部現有的技術而選,策略是以不增加公司同事之間跨越職能障礙為主要目標,其次是這個語言的資源,再其次是學習的門檻,因為有尚未開發過自動化的 QA ,所以選擇語言起手的難易度也需要列為考量。
公司內部的 backend 是 Java,而 front-end 則是 Vue.js,所以就剩下 JavaScript 及 Java 的選項能夠選擇,考量到學習的難易度與學習資源取得的容易度,就選了 JavaScript 了,其中考量到 JavaScript 的原因還有就是公司是有需要驗證 Web 上面顯示資料正確性的需求,所以如果學會了 JavaScript ,就可以 JavaScript 實作一些工具直接在 console 上執行來協助測試。
測試框架選擇
接下來就可以找 JavaScript 在 2021 的統計,可以參考這個網站,可以從圖表看到使用率最高的是 Jest。
使用率最高不代表他就是一個值得讓人使用的框架,所以再次參考了「時間推移體驗」這個數據,也可以觀察到 Jest 是一個會讓人願意再次使用的框架。
所以依照上面的數據,問了幾個身邊的朋友,得到的回饋也是好的,所以就選擇了 Jest 作為這次實作自動化的框架。
Modules 的分層
為了解決 Postman 所遇到的痛點,所以我們需要
- tests 層
- 使用其他 modules 來組成 test case 的地方
- request API 的部分要抽成 apis 的 module
- request payloads 的部分要抽成 requestPayloads 的 module
- 有一些需要共用的 lib 抽成 common 的 module
相依的關係可以畫成這個樣子
資料夾結構如下
├─apis
│ apis1.js
│ apis2.js
│ api{...}.js
│ index.js
│ requestHelper.js
├─common
│ jestExtend.js
│
├─requestPayloads
│ apis1Request.js
│ apis2Request.js
│ apis{...}Request.js
│ index.js
│
└─tests
│ singleApi1.test.js
│ singleApi2.test.js
│ singleApi{...}.test.js
│
└─stories
stories.test.js
各個 Modules 的介紹與實作
requestPayloads 資料夾
requestPayloads 資料夾中的 indejx.js 是用來統整各個 API 的預設 request payloads
const apis1= require('./apis1Request');
const apis2= require('./apis2Request');
module.exports = {
apis1,
apis2,
};
預設 request payloads 的定義為:Server 不會回應「lack of fields response」的 payload
以路徑 /apis1/products 為例子,會取 apis1 這個詞當作 file name,然後 products 當作 function name,apis1Request.js 內容就會如下:
const products = {
product_id: 1,
product_info: {
product_name: 'Car Engine',
},
};
module.exports = {
products,
};
假設 api 路徑為 /apis1/products,預期在 tests 中使用時則是這個樣子(第 4 行)
const apis = require('../apis');
const requestPayloads = require('../requestPayloads');
const apis1ProductsRequest = requestPayloads.apis1.products;
const result = await apis.apis1.products(apis1ProductsRequest );
expect(result.productName).toBe('something that expected product name');
apis 資料夾
apis 資料夾中的 index.js 是用來統整 apis1, apis2…等 api 的地方
const apis1 = require('./apis1');
const apis2 = require('./apis2');
module.exports = {
apis1,
apis2,
};
apis 資料夾中的 requestHelper.js 是用來管理 request API 的 HTTP method 的一層,如 get, post 等 在這一層會與 report 那一層作結合,多讓 jest 的 report 多帶一些在打 api request 的相關結果 程式碼單純以 post 為例子
const axios = require('axios');
/**
* @param {string} baseURL for base URL
* @param {object} headers for request headers
* @param {object} data for request payload
*/
async function postRequest(baseURL, headers, data) {
const result = await axios({
method: 'post',
url: baseURL,
headers: headers,
data: data,
})
.then((result) => {
return result;
})
.catch((err) => {
console.log(err);
});
return result;
}
module.exports = {
postRequest,
};
以路徑 /apis1/products 為例子,會取 apis1 這個詞當作 file name,然後 products 當作 function name,apis.js 中則會引用到 requestHelper.js 來 request API,apis.js code 如下
const {postRequest} = require('./requestHelper');
require('dotenv').config();
/**
* @param {object} request payload
* @return {object} response
*/
**async function products(data) {
const result = await postRequest(
`${configs.BASE_URL}/apis1/products`,
{
'x-api-key': process.env.API_KEY,
'content-type': 'application/json',
},
data,
);
return result;
}**
假設 api 路徑為 /apis1/products,預期在 tests 中使用時則是這個樣子(第 5 行)
const apis = require('../apis');
const requestPayloads = require('../requestPayloads');
const apis1ProductsRequest = requestPayloads.apis1.products;
const result = await apis.apis1.products(apis1ProductsRequest );
expect(result.productName).toBe('something that expected product name');
common 資料夾
這一層主要是放一些官方沒有提供的 library 實作,或是共用的 function,以 jest 沒有提供的 object contain 為例,就會新增 jestExtend.js,其 code 如下,在 scenario API test 中將會用到。
expect.extend({
toContainObject(received, argument) {
const pass = this.equals(
received,
expect.arrayContaining([expect.objectContaining(argument)]),
);
if (pass) {
return {
message: () =>
`expected ${this.utils.printReceived(
received,
)} not to contain object ${this.utils.printExpected(argument)}`,
pass: true,
};
} else {
return {
message: () =>
`expected ${this.utils.printReceived(
received,
)} to contain object ${this.utils.printExpected(argument)}`,
pass: false,
};
}
},
});
tests 資料夾
這一層就是 Jest 的使用層了,會在這一層中使用各個 modules 來組成 test case。
API 測試種類大致上可以分成兩種
- 單一 API 測試 (Single API test)
- API 情境測試 (Scenario API test)
單一 API 測試是指純粹只有這隻 API 是受測項目,完成測試的條件與其他 API 無關,是一個只需要使用單一一個 API 的測試項目,舉例來說 /products API,帶給他 payload,其中會 lack fields,或是沒有帶 header,就稱之為 single API test。
API 情境測試是指要完成一個測試情境而需要用到多個 API,這裡就會需要引用到多個不同的 API 來達成某種目的,舉例來說 call /prodcuts/update 更新一個 product 然後再透過 /products API 來取得預期被更新的 API,這邊就會引用到兩隻 API,這時候就稱之為 API 的情境測試。
Single API test,以 /products API 為例子,其 code 如下
const apis = require('../apis');
test('Get products, should return 200', async () => {
const result = await apis.products.get();
expect(result.status).toBe(200);
});
Scenario API test,以 /products, /products/create 為例子,其 code 如下
require('../../common/jestExtend');
const apis = require('../../apis');
const requestPayloads = require('../../requestPayloads');
test('Create product, should get the product at /products', async () => {
const createResult = await apis.products.create(
requestPayloads.products.create,
);
expect(createResult.status).toBe(200);
const result = await apis.products.get();
expect(result.data).toContainObject(requestPayloads.products.create);
});
結語和本篇沒提到的事
在這一篇中沒提到的有以下幾個事情
- json schema 的 validate
- config
依照目前的架構要擴充這兩件事情都可以很輕鬆,所以就沒有額外再寫出來記錄了
以上就是這一次整個 API automation 的思考與選擇的筆記
感謝各位大大看到這裡,如果有任何建議都可以跟我說,感謝 <(_ _)>
再次附上 repo: SQZ777/jest-for-api-automation-template (github.com)