深入淺出 JavaScript 軟體測試 — #2 測試的原理與細節

C.T. Lin
6 min readOct 28, 2021

--

上一篇介紹了測試時的 Mindset 跟一些好處,這篇要來講講測試的原理,已經比較熟測試的人可以考慮直接跳過這篇,去看之後幾篇針對一些狀況的寫法。

在 JavaScript test runner 這塊,我自己是 Facebook 的 Jest 的忠實擁護者,因為它包含了許多厲害的設計模式,給了充足彈性,是超過 50 個以上套件模組化的成果,包括 mock、timer 等等測試需要的重點機制也都有提供不錯的 API。

雖然之後幾篇還是會推薦 Jest,但 Jest 給我們的東西只是錦上添花,我們不一定要什麼高大上的 test runner 也能夠執行測試。這篇就要帶大家試試看用 Node.js 內建的東西來達成測試的目的。

測試的本質

測試的本質,說穿了就是按照「使用者使用的方式」去使用程式,驗證結果是否符合預期。

如果是寫前端網頁,那使用者就是看著瀏覽器滑動、點擊按鈕、輸入文字的人們,驗證的方式就是看顯示的畫面是否符合預期。

如果是提供程式套件或函式庫,那使用者就是會載入程式並提供輸入的工程師,驗證的方式則為看輸出是否符合預期。

另外一個需要驗證的部分是程式的副作用(Side Effect),包括呼叫第三方的程式、寄送 Email、帳戶付款等等。

而這個驗證的過程,可以是人工處理,而這邊要教的就是寫程式去驗證它,也就是我們所稱的自動化測試。

接下來我們利用一個最簡單的範例來看看測試是如何進行的:

// test.js
const assert = require('assert');
const a = true;console.log('⌛ 開始測試:a 應該為 true');
assert(a === true, '❌ 測試失敗:a 不為 true');
console.log('✅ 測試成功:a 為 true');
const b = false;console.log('⌛ 開始測試:b 應該為 true');
assert(b === true, '❌ 測試失敗:b 不為 true');
console.log('✅ 測試成功:b 為 true');

這邊使用到 Node.js 內建的 assert 模組,它的用法 assert(result, message) 是斷定輸入 result應該為 true,否則就拋出對應的錯誤訊息 message,這個我們一般稱為斷言(Assertion)。

這邊因為沒有 test runner 的輔助,我加了一些 console.log(...) 來幫助測試的顯示。

撰寫完後的測試可以直接用以下指令執行:

node test.js

可以看到 a 的部分成功了,b 的部分也如預期的噴出錯誤。

通常在 Node.js 的專案中,我們會在 package.json 裡面加上 test 這個 npm script:

// package.json
{
"scripts": {
"test": "node test.js"
}
}

這樣即可直接使用 npm test 這個指令來做測試。

再練習幾個簡單的測試

下面這段則是關於 Array.prototype.map(...) 的測試:

const assert = require('assert');const items = [1, 2, 3].map(number => `item_${number}`);assert(items[0] === 'item_1', '第 1 個 item 應該是 item1');
assert(items[1] === 'item_2', '第 2 個 item 應該是 item2');
assert(items[2] === 'item_3', '第 3 個 item 應該是 item3');

在寫斷言(Assertion)時,應該盡量使用能舒服看懂的語法,避免在斷言的部分大量使用程式邏輯跟變數,也可以減少測試程式寫錯的狀況。

下面這段則是關於 String.prototype.trim() 的測試:

const assert = require('assert');assert(' A'.trim() === 'A', '左邊能 trim 掉');
assert('A '.trim() === 'A', '右邊能 trim 掉');
assert(' A '.trim() === 'A', '兩邊都能 trim 掉');
assert(' A A '.trim() === 'A A', '中間不會被 trim 掉');

在寫的時候,我們適當的去思考各種用法應該成立的案例,但也不需要過度舉例,如果有兩個測試基本上是測試同一個會出錯的東西,那就留一個就好。

驗證 Side Effect

輸入輸出的驗證已經有概念了以後,要來看看怎麼驗證副作用(Side Effect)。

最常見的設計模式為,把副作用(Side Effect)隔離,用參數注入的方式提供,例如 callback 就算是種很經典的注入:

function doSomething(callback) {
// ... 很多邏輯(省略)
callback(10000);
}

在測試這樣的案例時,我們就可以簡單地使用假 function 跟狀態變數來做到判斷是否有執行以及執行時的輸入是否符合預期:

const assert = require('assert');function doSomething(callback) { /** **/ }let fnBeCalledWithArgs;
const fn = (...args) => { fnBeCalledWithArgs = args };
doSomething(fn);assert(fnBeCalledWithArgs, 'callback 有被 call');
assert(fnBeCalledWithArgs[0] === 10000, '被 call 時參數是 10000');

在寫程式時讓副作用(Side Effect)可以被測試,也是程式設計上的一個重要能力。

如果要利用 import 或是 require 來換掉模組,這因為實作上比較複雜,留待之後介紹 Jest 時再來討論。

結語

知道測試的原理這麼簡單後,也明確的知道測試的目的在於從使用者的角度直接驗證輸出與副作用(Side Effect)的部分,自然不會流於測試實作細節,也能從可測試性的評價不段的去優化程式設計。

接下來下一篇就要來介紹之後要使用的 test runner — Jest。

--

--

C.T. Lin

Architect @ Dcard. Author of Electron React Boilerplate and Bottender. JavaScript Developer.