前端測試框架 Jest
本文首發於:美團點評點餐,歡迎關注我們團隊
前端測試工具一覽
前端測試工具也和前端的框架一樣紛繁複雜,其中常見的測試工具,大致可分為測試框架、斷言庫、測試覆蓋率工具等幾類。在正式開始本文之前,我們先來大致了解下它們:
測試框架
測試框架的作用是提供一些方便的語法來描述測試用例,以及對用例進行分組。
測試框架可分為兩種: TDD (測試驅動開發)和 BDD (行為驅動開發),我理解兩者間的區別主要是一些語法上的不同,其中 BDD 提供了提供了可讀性更好的用例語法,至於詳細的區別可參見 The Difference Between TDD and BDD 一文。
常見的測試框架有 Jasmine, Mocha 以及本文要介紹的 Jest 。
斷言庫
斷言庫主要提供語義化方法,用於對參與測試的值做各種各樣的判斷。這些語義化方法會返回測試的結果,要麼成功、要麼失敗。常見的斷言庫有 Should.js, Chai.js 等。
測試覆蓋率工具
用於統計測試用例對代碼的測試情況,生成相應的報表,比如 istanbul 。
Jest
為什麼選擇 Jest
Jest 是 Facebook 出品的一個測試框架,相對其他測試框架,其一大特點就是就是內置了常用的測試工具,比如自帶斷言、測試覆蓋率工具,實現了開箱即用。
而作為一個面向前端的測試框架, Jest 可以利用其特有的快照測試功能,通過比對 UI 代碼生成的快照文件,實現對 React 等常見框架的自動測試。
此外, Jest 的測試用例是並行執行的,而且只執行發生改變的文件所對應的測試,提升了測試速度。目前在 Github 上其 star 數已經破萬;而除了 Facebook 外,業內其他公司也開始從其它測試框架轉向 Jest ,比如 Airbnb 的嘗試 ,相信未來 Jest 的發展趨勢仍會比較迅猛。
安裝
Jest 可以通過 npm 或 yarn 進行安裝。以 npm 為例,既可用npm install -g jest進行全局安裝;也可以只局部安裝、並在 package.json 中指定 test 腳本:
{n "scripts": {n "test": "jest"n }n}n
Jest 的測試腳本名形如*.test.js,不論 Jest 是全局運行還是通過npm test運行,它都會執行當前目錄下所有的*.test.js 或 *.spec.js 文件、完成測試。
基本使用
用例的表示
表示測試用例是一個測試框架提供的最基本的 API , Jest 內部使用了 Jasmine 2 來進行測試,故其用例語法與 Jasmine 相同。test()函數來描述一個測試用例,舉個簡單的例子:
// hello.jsnmodule.exports = () => Hello worldnn// hello.test.jsnlet hello = require(hello.js)nntest(should get "Hello world", () => {n expect(hello()).toBe(Hello world) // 測試成功n// expect(hello()).toBe(Hello) // 測試失敗n})n
其中toBe(Hello world)便是一句斷言( Jest 管它叫 「matcher」 ,想了解更多 matcher 請參考文檔)。寫完了用例,運行在項目目錄下執行npm test,即可看到測試結果:
若測試失敗,會標識出失敗的斷言位置,結果如下:
用例的預處理或後處理
有時我們想在測試開始之前進行下環境的檢查、或者在測試結束之後作一些清理操作,這就需要對用例進行預處理或後處理。對測試文件中所有的用例進行統一的預處理,可以使用 beforeAll() 函數;而如果想在每個用例開始前進行都預處理,則可使用 beforeEach() 函數。至於後處理,也有對應的 afterAll() 和 afterEach() 函數。
如果只是想對某幾個用例進行同樣的預處理或後處理,可以將先將這幾個用例歸為一組。使用 describe() 函數即可表示一組用例,再將上面提到的四個處理函數置於 describe() 的處理回調內,就實現了對一組用例的預處理或後處理:
describe(test testObject, () => {n beforeAll(() => {n// 預處理操作n })nn test(is foo, () => {n expect(testObject.foo).toBeTruthy()n })nn test(is not bar, () => {n expect(testObject.bar).toBeFalsy()n })nn afterAll(() => {n// 後處理操作n })n})n
測試非同步代碼
非同步代碼的測試,關鍵點在於告知測試框架測試何時完成,讓其在恰當的時機進行斷言。針對幾種常見的非同步代碼形式, Jest 也提供了相應的非同步測試語法。首先對於非同步回調,向其傳入並執行 done 函數, Jest 會等 done 回調執行結束後,結束測試:
// asyncHello.jsnmodule.exports = (name, cb) => setTimeout(() => cb(`Hello ${name}`), 1000)nn// asyncHello.test.jsnlet asyncHello = require(asyncHello.js)nntest(should get "Hello world", (done) => {n asyncHello(world, (result) => {n expect(result).toBe(Hello world)n done()n })n})n
此外,對於 Promise 控制的非同步代碼,可以直接在 then 回調中進行斷言,只要保證在用例中返回該 Promise 對象即可:
// promiseHello.jsnmodule.exports = (name) => {nreturn new Promise((resolve) => {n setTimeout(() => resolve(`Hello ${name}`), 1000)n })n}nn// promiseHello.test.jsnlet promiseHello = require(promiseHello.js)nnit(should get "Hello world", () => {n expect.assertions(1); // 確保至少有一個斷言被調用,否則測試失敗nreturn promiseHello(world).then((data) => {n expect(data).toBe(Hello world)n })n})n
Jest 也支持 async/await 語法的測試,無需多餘的操作,只要在 await 後進行斷言即可,和同步測試的寫法一致。
測試覆蓋率
Jest 內置了測試覆蓋率工具istanbul,要開啟,可以直接在命令中添加 --coverage 參數,或者在 package.json 文件進行更詳細的配置。
運行 istanbul 除了會再終端展示測試覆蓋率情況,還會在項目下生產一個 coverage 目錄,內附一個測試覆蓋率的報告,讓我們可以清晰看到分支的代碼的測試情況。比如下面這個例子:
// branches.jsnmodule.exports = (name) => {nif (name === Levon) {nreturn `Hello Levon`n } else {nreturn `Hello ${name}`n }n}nn// branches.test.jsnlet branches = require(../branches.js)nndescribe(Multiple branches test, ()=> {n test(should get Hello Levon, ()=> {n expect(branches(Levon)).toBe(Hello Levon)n });n// test(should get Hello World, ()=> {n// expect(branches(World)).toBe(Hello World)n// }); n})n
運行 jest --coverage 可看到生產的報告里展示了代碼的覆蓋率和未測試的行數:
如果我們把branches.test.js中的注釋去掉,跑遍測試對象中的所有分支,測試覆蓋率就是100%了:
在前端項目中使用
搭配React和其它框架
針對前端框架的測試, Jest 的一大特色就是提供了快照測試功能。首次運行快照測試,會讓 UI 框架生產一個可讀的快照,再次測試時便會通過比對快照文件和新 UI 框架產生的快照判斷測試是否通過。對於 React ,我們可以通過下面的方法生產一個快照:
import React from react;nimport Link from ../Link.react;nimport renderer from react-test-renderer;nnit(renders correctly, () => {nconst tree = renderer.create(n<Link page="http://www.facebook.com">Facebook</Link>n ).toJSON();n expect(tree).toMatchSnapshot();n});n
運行測試,我們可以看到生成一個快照文件如下:
exports[`renders correctly 1`] = `n<an className="normal"n href="http://www.facebook.com"n onMouseEnter={[Function]}n onMouseLeave={[Function]}n>n Facebookn</a>n`;n
這個可讀的快照文件以可讀的形式展示了 React 渲染出的 DOM 結構。相比於肉眼觀察效果的 UI 測試,快照測試直接由Jest進行比對、速度更快;而且由於直接展示了 DOM 結構,也能讓我們在檢查快照的時候,快速、準確地發現問題。
除了 React ,Jest 文檔中也提供了針對其他框架進行測試的指南。
無縫遷移
如果你的項目中已經使用了別的測試框架,比如 Mocha,有一個第三方工具jest-codemods可以自動把用例遷移成 Jest 的用例,降低了遷移成本。
後記:前端自動化測試,值不值得?
近幾年前端工程化的發展風起雲湧,但是前端自動化測試這塊內容大家卻似乎不太重視。雖然項目迭代過程中會有專門的測試人員進行測試,但等他們來進行測試時,代碼已經開發完成的狀態。與之相比,如果我們在開發過程中就進行了測試(直接採用 TDD 開發模式、或者針對既有的模塊寫用例),會有如下的好處:
- 保障代碼質量和功能的實現的完整度
- 提升開發效率,在開發過程中進行測試能讓我們提前發現 bug ,此時進行問題定位和修復的速度自然比開發完再被叫去修 bug 要快許多
- 便於項目維護,後續任何代碼更新也必須跑通測試用例,即使進行重構或開發人員發生變化也能保障預期功能的實現
當然,凡事都有兩面性,好處雖然明顯,卻並不是所有的項目都值得引入測試框架,畢竟維護測試用例也是需要成本的。對於一些需求頻繁變更、復用性較低的內容,比如活動頁面,讓開發專門抽出人力來寫測試用例確實得不償失。
而那些適合引入測試場景大概有這麼幾個:
- 需要長期維護的項目。它們需要測試來保障代碼可維護性、功能的穩定性
- 較為穩定的項目、或項目中較為穩定的部分。給它們寫測試用例,維護成本低
- 被多次復用的部分,比如一些通用組件和庫函數。因為多處復用,更要保障質量
以上就是我對前端測試的一點淺見,歡迎斧正。
Ref
聊一聊前端自動化測
推薦閱讀:
※最簡單的服務端渲染框架-Next.js快速入門
※深入Angular:組件(Component)動態載入
※Vue.js起手式+Vue小作品實戰
※Angular Articles 2017-01
※AntV - 我認為這是一個不嚴謹的錯誤