前端自動化單元測試初探

前言

本篇文章是我在學習前端自動化單元測試時的一些思路整理,之前也從未接觸過單元測試相關工具,如有錯漏,請讀者斧正。要是覺得不專業...你打我呀~~~
示例代碼的github地址:https://github.com/BboyAwey/auto-unit-test-testing

1. 什么是單元測試

單元測試(unit testing),是指對軟件中的最小可測試單元進行檢查和驗證。對于單元測試中單元的含義,一般來說,要根據實際情況去判定其具體含義,如C語言中單元指一個函數,Java里單元指一個類,圖形化的軟件中可以指一個窗口或一個菜單等。總的來說,單元就是人為規定的最小的被測功能模塊。單元測試是在軟件開發過程中要進行的最低級別的測試活動,軟件的獨立單元將在與程序的其他部分相隔離的情況下進行測試。——百度百科

對于JavaScript來說,通常也是針對函數、對象和模塊的測試

2. 為什么要進行單元測試

經驗表明一個盡責的單元測試方法將會在軟件開發的某個階段發現很多的Bug,并且修改它們的成本也很低。在軟件開發的后期階段,Bug的發現并修改將會變得更加困難,并要消耗大量的時間和開發費用。無論什么時候作出修改都要進行完整的回歸測試,在生命周期中盡早地對軟件產品進行測試將使效率和質量得到最好的保證。在提供了經過測試的單元的情況下,系統集成過程將會大大地簡化。開發人員可以將精力集中在單元之間的交互作用和全局的功能實現上,而不是陷入充滿很多Bug的單元之中不能自拔。——百度百科

3. 如何進行單元測試

3.1 選擇測試工具

在JavaScript世界中,我們需要至少三個工具來進行單元測試,這意味著每個工具都需要你進行選擇:

  • 測試管理工具
    測試管理工具是用來組織和運行整個測試的工具,它能夠將測試框架、斷言庫、測試瀏覽器、測試代碼和被測試代碼組織起來,并運行被測試代碼進行測試。測試工具有很多選擇,Selenium、WebDriver/Selenium 2、Mocha[1]、JsTestDriver、HTML Runners和Karma,我這里選擇使用Karma。(關于它們的對比,可以看看這篇文章:karma 測試框架的前世今生
  • 測試框架
    測是框架是單元測試的核心,它提供了單元測試所需的各種API,你可以使用它們來對你的代碼進行單元測試。JavaScript的測試框架可謂百花齊放,選擇太多了(可以參考List of unit testing frameworks),我這里選擇使用Mocha(關于它們中一些框架的對比,可以參考javascript單元測試
  • 斷言庫
    斷言庫提供了用于描述你的具體測試的API,有了它們你的測試代碼便能簡單直接,也更為語義化,理想狀態下你甚至可以讓非開發人員來撰寫單元測試。當然,你也完全可以不使用斷言庫,而是用自己的測試代碼去測試,不過幾乎沒有人會這么干,除非你自己實現了一個測試斷言庫。測試斷言庫的選擇也不少:better-assert、should.js、expect.js、chai.js等等(有關它們的對比,可以參考幾款前端測試斷言庫(Assertions lib)的選型總結我這里選擇chai.js

有了上面的三個工具,你就可以開始對你的node代碼進行測試了。但是如果想要對前端代碼進行測試,還需要另外一個工具:

  • 測試瀏覽器
    前端代碼是運行在瀏覽器中的,要對其進行單元測試,只能將其運行在瀏覽器上。目前大部分測試工具都支持調用和運行本地瀏覽器來進行測試,但如果你的測試僅僅是針對函數和模塊的單元測試,則完全可以使用一款無界面的瀏覽器:PhantomJs

另外,還有一個很重要的事情就是測試覆蓋率的統計。一般情況下你的測試管理工具會提供相關的覆蓋率統計工具,但是有些情況下它們提供的工具未必是你想要的。比如當被測試的代碼是經過了某些打包工具打包完了且被壓縮和混淆了,同時打包工具還混入了很多自己的代碼,這時覆蓋率的統計就容易不準確。所以為了避免這種情況,測試覆蓋率統計工具需要謹慎選擇,至少你得確認它支持你的打包工具已經打包好的代碼。

  • 測試覆蓋率統計工具
    Karma-Coverage是Karma官方提供的覆蓋率統計插件,自然成為項目的首選。

至此,我們需要的工具已經完備了,下面是選擇好的工具清單:

  • 測試管理工具:Karma
  • 測試框架:Mocha
  • 斷言庫:Chai
  • 測試瀏覽器:PhantomJs
  • 測試覆蓋率統計工具:Karma-Coverage

3.2 構建一個最基本的測試

3.2.1 配置好你的npm

初始化項目的package.json,并將需要的工具安裝到項目中,安裝完成后pakeage.json的devDependencies中應當出現下面的這些工具

"devDependencies": {
  "chai": "^3.5.0",
  "karma": "^1.3.0",
  "karma-chai": "^0.1.0",
  "karma-coverage": "^1.1.1",
  "karma-mocha": "^1.1.1",
  "karma-phantomjs-launcher": "^1.0.2",
  "mocha": "^3.0.2"
}
3.2.2 初始化Karma配置文件

在項目的根目錄運行

karma init

初始化Karma配置文件:


初始化Karma配置文件

生成的karma.conf.js只是最基本的karma配置,我們還需要手動修改一些地方。
在其中的frameworks一項中增加chai

frameworks: ['mocha','chai'],

然后在config.set({})中添加:

plugins : [
  'karma-mocha',
  'karma-chai',
  'karma-phantomjs-launcher'
],
3.2.3 提供需要測試的代碼并編寫測試代碼

在上文的初始化Karma配置時,已經告訴Karma,需要被測試的代碼和測試代碼放在src/test/文件夾中,所以我們應該在src/文件夾下提供要被測試的代碼,在test/文件夾下提供測試代碼:

項目文件結構

src/文件夾中新建index.js文件,在這個文件中添加兩個非常簡單的函數:

function isNum (num) {
  return typeof num === 'number'
}
function isString (str) {
  return typeof str === 'string'
}

然后在test/文件夾中新建index.test.js文件。通常,測試腳本與所要測試的源碼腳本同名,但是后綴名為.test.js(表示測試)或者.spec.js(表示規格)。在該文件中開始編寫測試代碼:

describe('index.js的測試', function () {
  it('1應該是數字', function() {
      // expect(isNum(1)).to.be.true
      isNum(1).should.equal(true)
  })
  it('"1" 應該是字符', function() {
      // expect(isString('1')).to.be.true
      isString('1').should.equal(true)
  })
})

編寫測試文件時,describeit都是由mocha提供的測試用api:

describe塊稱為"測試套件"(test suite),表示一組相關的測試。它是一個函數,第一個參數是測試套件的名稱("index.js的測試"),第二個參數是一個實際執行的函數。

it塊稱為"測試用例"(test case),表示一個單獨的測試,是測試的最小單位。它也是一個函數,第一個參數是測試用例的名稱("1應該是數字"),第二個參數是一個實際執行的函數。
——測試框架 Mocha 實例教程

如果測試不通過,測試套件和測試用例的描述都會在命令行輸出,告訴你哪里測試失敗了。
被測試代碼和測試代碼編寫完畢后,在項目根目錄輸入:

karma start

運行成功后,測試結果便會顯示在命令行中。并且這時你修改任意代碼,單元測試便會在你保存后自動運行。

3.2.4 統計測試覆蓋率

單元測試很多時候需要統計測試覆蓋率。使用karma-coverage來統計你的單元測試覆蓋率。修改karma.conf.js

preprocessors: {
  'src/*.js': ['coverage']
},
reporters: ['progress', 'coverage'],

然后再運行karma start,你的項目中便會多出一個coverage文件夾,文件夾中按照瀏覽器分了覆蓋率統計結果,我們使用的是PhantomJs,自然會有一個PhantomJs ..*文件夾,用瀏覽器打開index.html便可查看測試覆蓋率。

3.3 集成webpack

很多時候,項目中會用到webpack來進行打包,有了Webpack我們可以使用ES6甚至ES7語法,可以輕松打包Vue、React、Angular等主流框架,可以有Eslint代碼檢查。所以將Webpack集成進Karma后,我們可以使用最新的JS語法來編寫測試代碼,也可以對使用了主流框架的代碼進行單元測試了。

這里我們以使用ES6語法為目的,來演示如何集成Webpack。

3.3.1 安裝Webpack和Babel

首先安裝Webpack和karma-webpack插件

npm install webpack karma-webpack --save-dev

然后安裝babel

npm i --save-dev babel-loader babel-core babel-preset-es2015

3.3.2 在Karma中配置和使用Webpack

修改karma.conf.js,將webpack添加進去。

3.3.2.1 設置需要Webpack打包的文件

preprocessors中告訴karma需要Webpack打包的文件所在位置,這里我想同時在被測試代碼和測試代碼中使用ES6語法,那么理論上我除了將被測試代碼位置告訴Webpack之外,還需要將測試代碼的位置也告訴Webpack。

但如果你的代碼是模塊化的,使用了ES6的模塊系統,那么即使你將已經模塊化的index.js打包并好并注入到瀏覽器也是沒有用的,所以正確的做法應該是在你的測試代碼也就是index.test.js中引入index.js模塊進行測試。然而Webpack在處理index.test.js時會查找它的引用并自動打包過來,所以如果你的被測試代碼是模塊化的,Karma配置中的preprocessors中就應當去掉Webpack對被測試代碼的處理,同時files中也不需要讓Karma將被測試代碼放到瀏覽器了,這一切應當都交給Webpack來做:

preprocessors: {
  // 'src/*.js': ['webpack', 'coverage'],
  'src/*.js': ['coverage'],
  'test/*.js': ['webpack']
},
files: [
  // './src/*.js',
  './test/*.js'
],
3.3.2.2 配置好Webpack

在Karma中寫好Webpack的配置:

// webpack config
    webpack: {
      module: {
        loaders: [{
          test: /\\.js$/,
          loader: 'babel',
          exclude: /node_modules/,
          query: {
            presets: ['es2015']
          }
        }]
      }
    },

這一步有三點需要注意:

  1. 上面這些配置,完全可以獨立出來成為一個webpack.test.config.js,怎么樣,是不是很眼熟?
  2. 你可能已經注意到Webpack的配置中沒有entry,也沒有output,因為在Karma的preprocessors中已經告訴了Webpack需要打包哪些文件了,同時Karma也會處理好打包后文件的去向(當然是注入瀏覽器了,還能去哪,別忘記了還有karma-webpack這個插件在起作用)
  3. 測試的Webpack配置除了上面說的入口和出口,其余的配置跟普通使用Webpack沒有本質區別,所以從這里你完全可以發散思維,用Webpack去做你想做的~
3.3.2.3 添加karma-webpack插件

別忘記新版的Karma幾乎所有的工具都需要插件支持,這在老版本中是不需要的。所以得把karma-webpack添加到Karma的plugins中去

plugins : [
  'karma-mocha',
  'karma-chai',
  'karma-phantomjs-launcher',
  'karma-coverage',
  'karma-webpack'
],
3.3.2.4 在你的被測試代碼和測試代碼中使用ES6語法

首先是被測試代碼

function isNum (num) {
  return typeof num === 'number'
}
function isString (str) {
  return typeof str === 'string'
}
export default {
  isNum,
  isString
}

然后是測試代碼

import Index from '../src/index'
console.log('開始測試')
describe('index.js的測試', function () {
  it('1應該是數字', function() {
      // expect(isNum(1)).to.be.true
      Index.isNum(1).should.equal(true)
  })
  it('"1" 應該是字符', function() {
      // expect(isString('1')).to.be.true
      Index.isString('1').should.equal(true)
  })
})

我這里使用了ES6 中的模塊寫法,在index.js中輸出了一個帶有兩個方法的模塊,這時測試代碼中就需要引入這個模塊了,因為僅僅是簡單地將index.js輸出到瀏覽器是不會起任何作用的(webpack打包后,兩個需要測試的函數已經是私有變量了,前文也有所提及)。

這時再運行karma start,便能看到測試通過的結果,說明我們成功使用了webpack編譯了ES6。現在檢查一下代碼覆蓋率:

異常的代碼覆蓋率

會發現代碼覆蓋率無法正常檢測了。即使你注釋掉某個函數的測試用例,代碼覆蓋率仍舊是100%。這就是前文提到的,如果使用karma-coverage檢測Webpack打包后的代碼,就會出現這種情況。所以這里我們需要使用其它辦法來檢測代碼覆蓋率。

一般代碼覆蓋率的檢測是需要統計被測試代碼中需要測試的量,比如函數、行數等信息,然而打包后的代碼因為被混入了很多別的代碼,或者是變量被私有化了,這些統計就會出問題。所以最好的辦法是在打包之前進行統計。

方案其實有很多,比如isparta、isparta-instrumenter-loader、istanbul。這里選擇istanbul,因為karma-coverage用的就是它。同時,babel提供了一個插件babel-plugin-istanbul,能夠在babel編譯之前instrument你的ES6代碼,可以像下面這樣使用(參考babel-plugin-istanbul):
首先安裝babel-plugin-istanbul

npm install babel-plugin-istanbul --save-dev

然后將其放入到babel的插件選項中:

loaders: [{
  test: /\\.js$/,
  loader: 'babel',
  exclude: /node_modules/,
  query: {
    presets: ['es2015'],
    plugins: ['istanbul']
  }
}]

這里需要注意的是:

Note: This plugin does not generate any report or save any data to any file;it only adds instrumenting code to your JavaScript source code.To integrate with testing tools, please see the Integrations section.
—— cnpm babel-plugin-istanbul

這個插件的功能僅僅是instrument,不生成報告,所以報告的生成還是需要karma-coverage來完成的,所以之前有關karma-coverage的設置只需要將instrument部分也就是karma.conf.js中的preprocessors中的coverage去掉即可:

preprocessors: {
  // 'src/*.js': ['webpack', 'coverage'],
  // 'src/*.js': ['coverage'],
  'test/*.js': ['webpack']
},

這時再次運行karma start,便能看到測試通過的結果,說明我們成功使用了webpack編譯了ES6。現在檢查一下代碼覆蓋率:

正常的代碼覆蓋率

已經恢復正常了!按照上面的思路,我們完成了將Webpack配置到Karma中的工作,所以現在你可以使用Webpack來統一管理你的被測試代碼和測試代碼了。

4. 其它注意事項

4.1 關于npm

正常情況下國內是需要翻墻才能使用npm的,但你有兩個選擇:翻墻或者使用cnpm。我建議使用cnpm,安裝使用可以去cnpm官網查看詳細教程(非常簡單)

4.2 關于測試框架mocha和斷言庫chai.js

一個咖啡一個茶,雖然你已經能夠將它們運用到你的構建體系中去了,但這兩者的詳細API還是需要去熟悉和了解的,否則也沒辦法寫出高質量的測試代碼
mocha除了可以去mocha官方網站看英文文檔之外,還可以參考我翻譯的中文文檔:Mocha.js官方文檔翻譯 —— 簡單、靈活、有趣
chai.js則只需要去chai.js官方網站看API文檔,我也翻譯了TDD部分的API文檔:Chai.js斷言庫API中文文檔

4.3 關于BDD與TDD

BDD是行為驅動開發,TDD是測試驅動開發。但其實可以認為BDD是TDD的一個子集或分支,是測試驅動開發的升級版。具體可以參考這幾篇文章:


  1. Mocha既是測試工具,也是測試框架,其實有不少測試工具既是管理工具,也是測試框架 ?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,024評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,285評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,328評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,878評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,098評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,616評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,554評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,725評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,971評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,339評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,695評論 2 370

推薦閱讀更多精彩內容