單元測試是個很神奇的技能,筆者一開始是為了重構而學的,那時候單純的以為寫下測試只是方便讓邏輯不被改變,但是沒想到它帶來的好處比想像中的還多,因為在為程式寫測試時,往往能看到 coding 看不到的另一面。
本篇會學習 Jest 的基本用法,後面幾章會再說明如何將測試環境帶入 React 中。
單元測試是指為專案中每個單一行為做測試,只要每個單一行為都沒問題,那就能確保邏輯是正常的,
前端的測試框架不是只有 Jest,選擇原因是因為 Jest 和 React 的整合度較佳。除了 Jest 以外,常聽到的測試框架還有 mocha 。
輸入以下指令安裝 Jest :
npm install jest --save-dev
首先在專案的根目錄,也就是與 src 同一層的地方,建立一個新目錄__tests__:
|-src
|-__tests__
今後我們所有的測試案例都會在該目錄中撰寫,而 Jest 在執行測試時,會自動尋找檔名中含有 .test
的文件進行測試,所以我們在__tests__ 目錄下建立一個 index.test.js
作為一份將要被測試的檔案。
|-src
|-__tests__
|-index.test.js
將__tests__/index.test.js 打開,並寫下一個簡單的測試案例:
test('Check the result of 5 + ', () => {
expect(5 + 2).toBe(7);
});
把上方的 test 當作一個 function,負責描寫一個測試案例,它擁有兩個參數:
expect
用來描述被測試的內容, toBe
是測試內容的回傳值是否符合期望值,例如上方的測試內容為「 5 加上 2 期望會等於 7 」。然後當你寫下的時候會發現,怎麼 ESLint 會紅成這樣子:
那是因為 test
和 expect
都不是 JavaScript 原生提供的 Method ,所以被 ESlint 看見就會報錯,這裡先打開 ESlint 的設定檔 .eslintrc.js ,增加 Jest 執行環境:
module.exports = {
env: {
browser: true,
es6: true,
jest: true,
},
/* 其餘省略 */
}
再回到__tests__/index.test.js 就能看見警告消失了:
現在我們已經完成第一個測試案例了,我們可以用指令 jest
執行它,但考慮到我們是將 Jest 不是裝在全域環境中,以及今後測試方便,還是先打開 package.json,將執行測試的指令加到 script
:
"scripts": {
/* 其餘省略 */
"test": "jest",
},
加完後便可輸入 npm run test
來執行第一次測試:
結果內會顯示 Jest 測試了哪些 .test.js 檔案,還有每個測試( expect )內的結果( toBe )是否正確符合,符合的話會輸出 PASS。
我們再打開__tests__/index.test.js,將 toBe(7)
改成 8,並重新執行測試:
結果會狠狠地出現紅色的錯誤,並且告訴你程式運行後的結果應該是 7,不是 8。
除了 toBe
這個斷言外,還有許多其他的斷言方式,例如物件的話可以用 toEqual
,想得到相反的結果則是在斷言前串上一個 not
:
test('Check the result of 5 + 2', () => {
expect(5 + 2).not.toBe(8);
});
測試結果就會變成正確,因為加上 not
後就變成「不等於 8」。
到這裡也許會覺得很奇怪,為什麼要為一個早就知道結果是正確的東西做測試,沒意義不是嗎?
其實不能這樣想,因為就現在而言,Function 就是長那個樣子,輸入什麼就輸出什麼,所以才會覺得為了不會變的結果寫下測試按理很多餘。
程式其實是長遠的,寫下多多的程式就會埋下多多的 Bug,就算沒有 Bug,客戶的要求也是一堆,相信各位都一定會有改了這個,壞了那個的經驗吧?
如果我們要寫下完整的測試案例,也許就需要將每一項測試分類,並且引入作用域,而 describe
可以做到這件事情,它的結構和 test
相同,擁有兩個參數,一個是描述該作用域的測試內容,第二個一樣是個 Function,我們會將同作用域的測試都放入其中,例如:
describe('Check add', () => {
test('Check the result of 5 + 2', () => {
expect(5 + 2).not.toBe(8);
});
test('Check the result of 5 + 3', () => {
expect(5 + 3).toBe(8);
});
});
describe('Check sub', () => {
test('Check the result of 5 - 2', () => {
expect(5 - 2).not.toBe(1);
});
test('Check the result of 5 - 3', () => {
expect(5 - 3).toBe(2);
});
});
上方一口氣新增了另外三個測試案例,雖然都沒有意義,但是根據加法和減法替它們用 describe
做分類,執行測試的結果也會稍微不同:
當然作用域不只是這樣子美觀分類而已,在作用域內還有幾個生命週期可以調用:
beforeAll
:所在區域內會第一個執行。beforeEach
:每一次的測試前會先執行。afterAll
:所在區域內最後一個執行。afterEach
:每一次的測試後會馬上執行。例如我們在 Check add 的 describe
中使用 beforeEach
和 afterAll
,並讓它們在 console 中留下文字:
describe('Check add', () => {
beforeEach(() => {
console.log('每次執行測試前執行哦');
});
afterAll(() => {
console.log('所有測試結束後才看得見我');
});
test('Check the result of 5 + 2', () => {
expect(5 + 2).not.toBe(8);
});
test('Check the result of 5 + 3', () => {
expect(5 + 3).toBe(8);
});
});
/* 其餘省略 */
執行測試後可以看到生命週期:
通常我們會使用 afterEach
,在每一次測試後重置一些因為測試出現的副作用,像是重置初始資料等等,之後的篇章會再使用到它。
到目前為止,本文的案例為了方便講解,都直接以一個結果,像是「5 + 2」,然後去斷言是不是 7,但是現實中是不會有這種情況的,就像文章中一開始提到的,單元測試是以一個最小單一行為去做測試,有可能是個 function,也有可能是整個 module,因此本文最後展示如何直接使用 function 做測試。
在 src 下創建 utils,我們會將所有的共用方法都放在該目錄下:
|-src
|-utils
/* 其餘省略 */
然後在 utils 下新增一個 math.js,並創建兩個 function,分別是加法和減法,記得要將它們 export:
export const add = (a, b) => a + b;
export const sub = (a, b) => a - b;
回到 index.test.js ,將 src/utils/math.js 的 add
和 sub
import:
import { add, sub } from '../src/utils/math';
然後將 Check sub 和 Check add 中的加減法都替換成 add
和 sub
執行,以下只展示修改後的 Check sub , Check add 讓大家自行練習,文章中的最後也有提供 GitHub 可以看範例程式碼:
describe('Check sub', () => {
test('Check the result of 5 - 2', () => {
expect(sub(5, 2)).not.toBe(1);
});
test('Check the result of 5 - 3', () => {
expect(sub(5, 3)).toBe(2);
});
});
改完後輸入指令執行會發生錯誤:
因為測試不會讓 index.test.js 經過編譯,所以讀到 import
這個語法就出錯了,這時候我們可以手動在專案目錄下新增一個 .babelrc.js,並在裡面輸入之前下載來負責編譯 ES6 語法的 preset:
module.exports = {
presets: ['@babel/preset-env'],
};
這麼一來,將測試案例會在執行時自動經過編譯,結果也就不會有問題了。
本文的範例程式碼會提供在 GitHub 上,歡迎各位參考:)
關於測試的基本本來一篇文章就想講完,但是不知不覺就越打越長,到最後連 Mock 的觀念都沒有提到,因此決定將 Mock 抽離到下一篇文章,也還好當時有預留一個位置,剛好在這時候補上。
如果文章中有任何問題,或是不理解的地方,都可以留言告訴我!謝謝大家!
我覺得下次可以考慮跟TDD做搭配
TDD 會顛覆你的想法
太感人了,你居然一篇一篇文章看
告訴我你在哪裡,說不定可以當個朋友 XD
我是在台北阿XD
https://github.com/nttu94507/React2
這是我參照你文章寫code的github
與你的不同是
系統與套件版本的不同
我把以前做過的東西盡量都留下
順便練習git 的基礎用法
BTW 對於新手來說
這樣step by step 的方式很好
雖然有點小缺點但可以忽略不計了
你真的太讚了,直接實作起來!
我真的希望這個系列對大家有幫助
哈哈哈哈 小缺點的部分再麻煩你告訴我了
BTW 我卻是在台南 XD
Hi 神Q大大
我嘗試在vscode建立 .babelrc.js,但是不被允許
所以上網查了一下jest官網,現在可以用設定babel.config.js解決~
資料來源: https://jestjs.io/docs/tutorial-react
我兩種都可以
babel.config.js
.babelrc.js