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