學習目標:
了解為什麼我們需要 unit test
了解什麼是 unit test
了解如何寫 unit test
了解如何測試一個 function
測試程式的作用是「模擬外部如何使用目標程式,驗證目標程式的行為是否符合預期」。
利用 console.log()
測試
在前面幾個章節,我們通常會使用 console.log(),來測幾個範例確認是否正確。也需要考慮到邊界條件(edge case)來進行測試。
// 以測試 `repeat` 函式為例:
function repeat(str, times) {
let result = ''
for (let i = 0; i < times; i += 1) {
result += str
}
return result
}
console.log(repeat('a', 5)); // 印出 aaaaa
console.log(repeat('z!Z!Z!!', 2)); // 印出 z!Z!Z!!z!Z!Z!!
console.log(repeat('', 3)); // 印出空字串
console.log(repeat('abc', 0)); // 印出空字串
接著優化測試資料:直接判斷函式執行結果是否正確。
// 優化測試資料:
console.log(repeat('a', 5) === 'aaaaa');
console.log(repeat('z!Z!Z!!', 2) === 'z!Z!Z!!z!Z!Z!!');
console.log(repeat('', 3) === '');
console.log(repeat('abc', 0) === '');
// 均印出 true
這是最簡單的測試方法,但這麼做的缺點是很難「規模化」。我們可以利用別人寫好的框架來便利測試。
利用現成的框架 Jest 測試
- 用 npm 下載 Jest:輸入指令
npm install -save-dev jest
- 利用模組將「測試」與「要測試的 Function」分開。
- 在要測試的檔案 index.js 加上:
module.exports = '要輸出的值'
function repeat(str, times) {
let result = ''
for (let i = 0; i < times; i += 1) {
result += str
}
return result
}
module.exports = repeat
- 建立 index.test.js 檔案:
touch index.test.js
,習慣用test.js
取名
並在檔案中引用 index.js 輸出的值:var repeat = require('./引入的檔案')
// 可輸入 node index.test.js 測試是否引入成功
var repeat = require('./index')
console.log(repeat('a', 5));
// 印出 aaaaa,引入成功
- 在 index.test.js 加入 Jest 語法:
test('描述文字', '要做的測試')
var repeat = require('./index')
test('a 重複 5 次應該要等於', function() {
expect(repeat('a', 5)).toBe('aaaaa');
})
- 更新 package.json 檔案的
"scripts"
:加入"test": "jest"
- 如此即可運行
npm run test
進行測試,看到 PASS 可知測試有成功:
之所以要用 npm 來跑 jest,而不是直接在終端機輸入 jest 指令,是因為 jest 只安裝在該專案底下,要使用時才會拿出來用。
若只想測特定檔案,可以修改 "scripts"
:"test": "jest index.test.js"
,後面加上檔名。
或是用 npx jest index.jest.js
,同樣能夠執行測試:
也可以多跑幾個測式:
var repeat = require('./index')
test('a 重複 5 次應該要等於aaaaa', function() {
expect(repeat('a', 5)).toBe('aaaaa');
});
test('abc 重複 1 次應該要等於abc', function () {
expect(repeat('abc', 1)).toBe('abc');
});
test(' "" 重複 10 次應該要等於 ""', function () {
expect(repeat('', 10)).toBe('');
});
也可以把測試項目放在 describe()
函式裡,這種寫法會更有結構一點:
// 架構:
`describe('測試 XX', function(){
test('名稱', function() {
expect('回傳值').toBe('預期結果');
})
})`
var repeat = require('./index')
describe('測試 repeat', function() {
test('a 重複 5 次應該要等於aaaaa', function () {
expect(repeat('a', 5)).toBe('aaaaa');
});
test('abc 重複 1 次應該要等於abc', function () {
expect(repeat('abc', 1)).toBe('abc');
});
test(' "" 重複 10 次應該要等於 ""', function () {
expect(repeat('', 10)).toBe('');
});
})
Unit test 單元測試
單元測試指的是測試一個工作單元(a unit of work)的行為。上述範例測試單一函式,就是一種單元測試。可用來確認每個 Unit 是否正確,也能夠進行規模化測試。
補充:如何在 Windows 上執行 Jest 也能有紅綠標籤
方法:把 package.json
裡的 "script"
中的內容改為 "test": "jest --colors"
,FAIL 和 PASS 標籤就會是紅綠標籤。
奇怪的是,在 git commit 時都沒有問題,在執行 Jest 時卻出現亂碼。不太確定是不是因為更改 locale 才解決的,總之介面很神奇的變成中文了,可喜可賀。
參考資料:
- Colorful output in Bash terminal does not work (–colors option)
先寫測試再寫程式:TDD
Test-driven Development(測試驅動開發),是一種開發流程,簡言之就是「先寫測試在開發」。相較於傳統的「先開發在寫測試」模式,TDD 有幾項優點:
- 能確保測試程式的撰寫
- 從使用方觀點切入,有助於在開發初期釐清程式介面如何設計
- 便於日後 Debug
實作 TDD
可利用 Jest 模組,先建立 test()
架構,再來撰寫主要程式碼。步驟可參考:TDD 開發五步驟,帶你實戰 Test-Driven Development 範例
步驟一、選定一個目標功能,來新增測試案例
先寫好測試預期結果,並盡量列出邊界條件。在這個步驟還不會撰寫目標程式內容。這裡用 reverse
函式為例:
// 先寫出預期結果:
var reverse = require('./index')
describe('測試 reverse', function() {
test('123 reverse 要等於 321', function () {
expect(reverse('123')).toBe('321');
});
test('!!! reverse 要等於 !!!', function () {
expect(reverse('!!!')).toBe('!!!');
});
test(' "" reverse 要等於 ""', function () {
expect(reverse('')).toBe('');
});
})
步驟二、執行測試,得到 Failed(紅燈)
因為還沒撰寫目標程式,結果就會是 Failed。此步驟目的是確保測試程式可執行,沒有語法錯誤。
步驟三、實作「最低限度」的產品程式
開始寫程式,以能夠通過測試為目標,不求將程式碼優化一步到位。完成到一個階段就可運行 jest 查看是否有錯誤:
//開始寫程式
function reverse(str) {
let result = ''
for (let i = str.length - 1; i >= 0; i -= 1) {
result += str[i]
}
return result
}
module.exports = reverse
步驟四:再次執行測試,得到 Passed(綠燈)
在這個階段,即完成一個可運作且正確的程式版本,包含產品程式和測試程式。
步驟五:重構程式
最後是優化「產品程式」和「測試程式」的程式碼,因為測試程式也是專案需維護的一部份。如此即可提升程式的可讀性、可維護性、擴充性。