前言
隨著Web業務的日益復雜化和多元化,前端開發也有了前端工程化的概念,前端工程化成為目前前端架構中重要的一環,本質上也是軟件工程的一種,因此我們需要從軟件工程的角度來研究前端工程,而自動化測試則是軟件工程中重要的一環。本文就研究一下前端領域中的自動化測試,以及如何實踐。
什么是單元測試
單元測試(unit testing),是指對軟件中的最小可測試單元進行檢查和驗證。對于單元測試中單元的含義,一般來說,要根據實際情況去判定其具體含義,如C語言中單元指一個函數,Java里單元指一個類,圖形化的軟件中可以指一個窗口或一個菜單等。總的來說,單元就是人為規定的最小的被測功能模塊。單元測試是在軟件開發過程中要進行的最低級別的測試活動,軟件的獨立單元將在與程序的其他部分相隔離的情況下進行測試。——百度百科
為何要測試
以前沒有編寫和維護測試用例的習慣,在項目的緊張開發周期中也沒時間去做這個工作,相信有不少開發人員都不會重視單元測試這項工作。在真正寫了一段時間基礎組件后,才發現自動化測試有很多好處:
- 提升代碼質量。雖不能說百分百無bug,但至少說明測試用例覆蓋到的場景是沒有問題的。
- 能快速反饋,能確定UI組件工作情況是否符合自己預期。
- 開發者會更加信任自己的代碼,也不會懼怕將代碼交給別人維護。后人接手一段有測試用例的代碼,修改起來也會更加從容。測試用例里非常清楚的闡釋了開發者和使用者對于這段代碼的期望和要求,也非常有利于代碼的傳承。
當然由于維護測試用例也是一大筆開銷,還是要基于投入產出比來做單元測試。對于像基礎組件、基礎模型之類的不常變更且復用較多的部分,可以考慮寫測試用例來保證質量,但對于迭代較快的業務邏輯及生存時間不長的部分就沒必要浪費時間了。
因此github上看到的star較多的牛逼開源前端項目基本上都是有測試代碼的,看來業界大牛們都是比較重視單元測試這塊的。
相關概念
TDD
TDD是Test Driven Development 的縮寫,也就是測試驅動開發。
通常傳統軟件工程將測試描述為軟件生命周期的一個環節,并且是在編碼之后。但敏捷開發大師Kent Beck在2003年出版了 Test Driven Development By Example 一書,從而確立了測試驅動開發這個領域。
TDD需要遵循如下規則:
- 寫一個單元測試去描述程序的一個方面。
- 運行它應該會失敗,因為程序還缺少這個特性。
- 為這個程序添加一些盡可能簡單的代碼保證測試通過。
- 重構這部分代碼,直到代碼沒有重復、代碼責任清晰并且結構簡單。
- 持續重復這樣做,積累代碼。
TDD具有很強的目的性,在直接結果的指導下開發生產代碼,然后不斷圍繞這個目標去改進代碼,其優勢是高效和去冗余的。所以其特點應該是由需求得出測試,由測試代碼得出生產代碼。打個比方就像是自行車的兩個輪子,雖然都是在向同一個方向轉動,但是后輪是施力的,帶動車子向前,而前輪是受力的,被向前的車子帶動而轉。
BDD
所謂的BDD行為驅動開發,即Behaviour Driven Development,是一種新的敏捷開發方法。它更趨向于需求,需要共同利益者的參與,強調用戶故事(User Story)和行為。2009年,在倫敦發表的“敏捷規格,BDD和極限測試交流”中,Dan North對BDD給出了如下定義:
BDD是第二代的、由外及內的、基于拉(pull)的、多方利益相關者的(stakeholder)、多種可擴展的、高自動化的敏捷方法。它描述了一個交互循環,可以具有帶有良好定義的輸出(即工作中交付的結果):已測試過的軟件。
它對TDD的理念進行了擴展,在TDD中側重點偏向開發,通過測試用例來規范約束開發者編寫出質量更高、bug更少的代碼。而BDD更加側重設計,其要求在設計測試用例的時候對系統進行定義,倡導使用通用的語言將系統的行為描述出來,將系統設計和測試用例結合起來,從而以此為驅動進行開發工作。
大致過程:
從業務的角度定義具體的,以及可衡量的目標
找到一種可以達到設定目標的、對業務最重要的那些功能的方法
然后像故事一樣描述出一個個具體可執行的行為。其描述方法基于一些通用詞匯,這些詞匯具有準確無誤的表達能力和一致的含義。例如,expect, should, assert
尋找合適語言及方法,對行為進行實現
測試人員檢驗產品運行結果是否符合預期行為。最大程度的交付出符合用戶期望的產品,避免表達不一致帶來的問題
覆蓋率
如何衡量測試腳本的質量呢?其中一個參考指標就是代碼覆蓋率(coverage)。
什么是代碼覆蓋率?簡而言之就是測試中運行到的代碼占所有代碼的比率。其中又可以分為行數覆蓋率,分支覆蓋率等。具體的含義不再細說,有興趣的可以自行查閱資料。
雖然并不是說代碼覆蓋率越高,測試的腳本寫得越好,但是代碼覆蓋率對撰寫測試腳本還是有一定的指導意義的。
前端單測工具棧
測試框架
主要提供了清晰簡明的語法來描述測試用例,以及對測試用例分組,測試框架會抓取到代碼拋出的AssertionError,并增加一大堆附加信息,比如那個用例掛了,為什么掛等等。目前比較流行的測試框架有:
- Jasmine: 自帶斷言(assert),mock功能
- Mocha: 框架不帶斷言和mock功能,需要結合其他工具,由tj大神開發
- Jest: 由Facebook出品的測試框架,在Jasmine測試框架上演變開發而來
斷言庫
斷言庫提供了很多語義化的方法來對值做各種各樣的判斷。
mock庫
- sinon.js:使用Sinon,我們可以把任何JavaScript函數替換成一個測試替身。通過配置,測試替身可以完成各種各樣的任務來讓測試復雜代碼變得簡單。支持 spies, stub, fake XMLHttpRequest, Fake server, Fake time,很強大
測試集成管理工具
- karma:Google Angular 團隊寫的,功能很強大,有很多插件。可以連接真實的瀏覽器跑測試。能夠用一些測試覆蓋率統計的工具統計一下覆蓋率;或是能夠加入持續集成,提交代碼后自動跑測試用例。
測試腳本的寫法
通常,測試腳本與所要測試的源碼腳本同名,但是后綴名為.test.js(表示測試)或者.spec.js(表示規格)。
// add.test.js
var add = require('./add.js');
var expect = require('chai').expect;
describe('加法函數的測試', function() {
it('1 加 1 應該等于 2', function() {
expect(add(1, 1)).to.be.equal(2);
});
});
上面這段代碼,就是測試腳本,它可以獨立執行。測試腳本里面應該包括一個或多個describe塊,每個describe塊應該包括一個或多個it塊。
describe塊稱為"測試套件"(test suite),表示一組相關的測試。它是一個函數,第一個參數是測試套件的名稱("加法函數的測試"),第二個參數是一個實際執行的函數。
describe干的事情就是給測試用例分組。為了盡可能多的覆蓋各種情況,測試用例往往會有很多。這時候通過分組就可以比較方便的管理(這里提一句,describe是可以嵌套的,也就是說外層分組了之后,內部還可以分子組)。另外還有一個非常重要的特性,就是每個分組都可以進行預處理(before、beforeEach)和后處理(after, afterEach)。
it塊稱為"測試用例"(test case),表示一個單獨的測試,是測試的最小單位。它也是一個函數,第一個參數是測試用例的名稱("1 加 1 應該等于 2"),第二個參數是一個實際執行的函數。
大型項目有很多測試用例。有時,我們希望只運行其中的幾個,這時可以用only方法。describe塊和it塊都允許調用only方法,表示只運行某個測試套件或測試用例。此外,還有skip方法,表示跳過指定的測試套件或測試用例。
describe.only('something', function() {
// 只會跑包在里面的測試
})
it.only('do do', () => {
// 只會跑這一個測試
})
react 單測示例一
該框架采用 karma + mocha + chai + sinon 的組合, 是一種采用工具較多,同時自由度較高的解決方案。雖然工具庫使用的較多,但有助于理解各個工具庫的作用和使用,也有助于加深對前端單元測試的理解。
其中React的測試庫使用 enzyme,React測試必須使用官方的測試工具庫,但是它用起來不夠方便,所以有人做了封裝,推出了一些第三方庫,其中Airbnb公司的Enzyme最容易上手。
關于該庫的 api 使用可參考:
react 單測示例二
該框架只采用了Jest,是比較簡潔的方案,同樣也使用了 enzyme。
Jest 是Facebook開發的一個測試框架,它集成了測試執行器、斷言庫、spy、mock、snapshot和測試覆蓋率報告等功能。React項目本身也是使用Jest進行單測的,因此它們倆的契合度相當高。
之前僅在其內部使用,后開源,并且是在Jasmine測試框架上演變開發而來,使用了熟知的expect(value).toBe(other)這種斷言格式。
PS: 目前 enzyme 使用時需要加入設置如下:
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });
上面兩個框架方案中都有加入該配置的方法,詳見示例。