在先後端分離大趨勢的今天,經過模塊的方式來管理代碼彷佛比之前任什麼時候候都容易。組件都是由JavaScript編寫,且組件自己就是一個狀態機,這爲咱們編寫測試帶來了很多便利性。前端
然而,在如此好的環境下測試彷佛依然得不到許多前端人員的重視(包括我)。提及來也是,即使組件化已經深刻人心,但實現組件化的方式卻多種多樣。React, Vue, Angular這些框架各有各的哲學思想,時間都花在折騰這些工具上了,好好寫測試漸漸成了奢望。爲此這篇文章我但願能從琳琅滿目的前端工具中脫離出來,簡單地闡述一些,關於單元測試最基礎,或者說稍微本質性的東西。node
我的覺得,不管一個單元測試有多麼複雜,它本質上應該能夠劃分爲下面這些部件webpack
每個部件都有表明性的程序庫,不一樣的社區可能有不一樣的選擇(不限於JS社區),但我的以爲他們之間區別並非很大。咱們以爲測試複雜,很大一部分緣由腳手架致使的,爲了把測試集成到項目開發流程中除了須要安裝相關的測試依賴庫之外還須要搭配Webpack
,固然可能也會包括較爲流行的前端框架React
, Vue
等等。git
不少時候會形成一種現象就是package.json
裏面的依賴包,真正生產環境中會使用的只不過有1-3個,然而開發人員所要用到的單單用於測試的依賴包就有十幾二十個,怎能不讓人生畏?爲了排除這些干擾,我只在Node平臺上面來介紹這些單元測試的基本部件。github
Mocha是目前JS開源社區用得比較多的一個測試框架,在代碼組織層面上它充當了我前面所說的測試聲明
的角色,咱們能夠用它所提供的DSL組織測試代碼。另外它也包含命令行工具,在Node平臺上能夠執行相關的命令來運行已經定義好的測試。下面我編寫一個簡單的函數並測試它(原則上我應該先寫測試再寫函數)。web
// src/handle.js
exports.handleByCallback = (string, callback) => {
return callback(string)
}
複製代碼
這是一個很簡單的函數,經過傳入回調函數來處理相關的字符串參數,並返回結果。Mocha如何安裝我這裏就很少說了,下面是我寫的簡單的測試文件json
// test/handle.spec.js
const assert = require('assert');
const handle = require('../src/handle.js')
describe('Test handle module', () => {
it('handle string by callback method', () => {
assert.equal(typeof handle.handleByCallback, 'function')
const callback = function c(string) {
c.called = true
c.callCount ++
return String.prototype.repeat.call(string, 2)
}
callback.called = false
callback.callCount = 0
assert.equal(handle.handleByCallback("hello", callback), "hellohello")
assert.equal(callback.called, true)
assert.equal(callback.callCount, 1)
assert.equal(handle.handleByCallback("World", callback), "WorldWorld")
assert.equal(callback.called, true)
assert.equal(callback.callCount, 2)
})
})
複製代碼
這測試彷佛有點長,試着運行一下Mocha的命令行工具並指定對應的測試文件,看看這杯摩卡好很差喝。後端
看到綠了我就放心了。可是爲了寫個測試咱們還得費心去定義一個函數,這使得咱們的測試代碼有點長了。耐心看下去,接下來你會知道怎麼去優化它。瀏覽器
Sinon.js是我比較喜歡的一個測試輔助工具。我能夠用它來建立仿真函數,或者API的請求,加快測試編寫的進程,使得測試代碼更爲精煉且可讀性更高。接下來我就用這個函數庫來優化上面所編寫的測試代碼。前端框架
上面的例子中,我本身建立了一個回調函數,而且爲函數設定了相關屬性。測試完結以後將會確認兩個事情
細想一下若是每次咱們都要手動地去定義回調函數及其相關屬性的話代碼將會愈來愈長,測試也將愈加麻煩。這種時候咱們可能會考慮把它封裝成一個工廠函數,自動幫咱們生成這類函數。畢竟比起內部邏輯咱們更關心回調函數的返回值不是嗎?這其實就是一種仿真的手段,Sinon很好地協助咱們作好了這個事情,下面是我利用Sinon優化過的測試代碼
...
const sinon = require('sinon');
describe('Test handle module', () => {
.....
it('handle string by callback method using sinon', () => {
assert.equal(typeof handle.handleByCallback, 'function')
const callback = sinon.fake.returns('Hello World') // 仿真一個老是返回'Hello World'的函數
assert.equal(handle.handleByCallback("hello", callback), "Hello World")
assert.equal(callback.called, true)
assert.equal(callback.callCount, 1)
assert.equal(callback.lastArg, 'hello')
assert.equal(handle.handleByCallback("good job", callback), "Hello World")
assert.equal(callback.called, true)
assert.equal(callback.callCount, 2)
assert.equal(callback.lastArg, 'good job')
})
...
})
複製代碼
上述代碼最值得關注的地方在於,只須要
const callback = sinon.fake.returns('Hello World')
複製代碼
就可以仿真出一個總會返回"Hello World"的回調函數,做爲一個測試的輔助函數,足矣。
除此以外,仿真函數裏面會包含許多可用的屬性,具體可參考文檔。我這裏只列舉了幾個對於當前測試比較有意義的屬性called
-記錄函數是否被調用, callCount
-函數被調用的次數, lastArg
-調用函數的最後一個參數,測試效果以下
固然這只是比較簡單的場景,Sinon的能力還遠不止如此。我以爲仿真是測試裏面的難點,畢竟並非全部場景都如同上述例子那般簡單粗暴,這方面我本身也在慢慢克服着,與君共勉。
Node.js自己就有斷言庫,就是我上文引入的assert
。然而不少時候咱們的測試代碼並非在Node端運行,而是要把相關的代碼加載到對應的瀏覽器中,如Chrome,Firefox等等。這種時候就得藉助第三方庫了。這裏我簡單介紹一下Chai斷言庫,它的的斷言語句十分豐富,下面我用簡單的expect
語句來重寫上面的邏輯
...
const chai = require('chai')
const { expect } = chai
describe('Test handle module', () => {
.....
it('handle string by callback method using sinon and chai', () => {
expect(typeof handle.handleByCallback).to.equal('function')
const callback = sinon.fake.returns('Hello World')
expect(handle.handleByCallback('hello', callback)).to.equal("Hello World")
expect(callback.called).to.be.true
expect(callback.callCount).to.equal(1)
expect(callback.lastArg).to.equal('hello')
expect(handle.handleByCallback('good job', callback)).to.equal("Hello World")
expect(callback.called).to.be.true
expect(callback.callCount).to.equal(2)
expect(callback.lastArg).to.equal('good job')
})
...
})
複製代碼
再次運行node_modules/mocha/bin/mocha test/handle.spec.js
命令,結果以下
測試效果跟以前同樣。從語法上來看使用了Chai以後斷言語句彷佛有了點Ruby範兒。最後來咱們聊聊測試Runner。
測試Runner顧名思義就是測試的運行者,上面的例子中每次咱們都是經過Mocha的命令行程序來運行相應的測試程序,其中Mocha就充當了測試Runner的角色,然而正式的業務中測試可能會分散到多個不一樣的目錄下,咱們可能會在測試或者開發文件中運用較新的JS語法,或者是相關的框架的DSL,爲了使這堆代碼可以在瀏覽器端運行就少不了預編譯。
前面的例子都是用Node來寫,相對比較簡單且容易理解,可是前端測試註定要複雜的多,我所理解的前端測試對於Runner有以下要求
這看起來彷佛有點難,但JS社區中有一個叫Karma的框架可以大大簡化上述工做。它能夠簡單地與Webpack結合,並利用已有的Webpack配置來編譯咱們的代碼,只須要簡單的配置就能夠識別並運行相關的測試。它還能以服務的方式運行,在開發過程當中監測文件的改動並從新運行測試。因爲篇幅有限就不對它的配置進行更多說明了,有具體需求再去查看文檔便可。
PS: 雖然說Angular最近彷佛不怎麼受待見,但可別由於Karma是Angular團隊出的就對它視而不見啊。
Q: 爲何沒有Webpack?
A: 說實話確實也計劃過在文章裏面添加這樣一個東西,後來寫着寫着仍是放棄了。Webpack有豐富的插件系統,確實在某種程度上給予咱們開發人員必定的便利性。可是我的以爲它是使得咱們現在前端領域變得如此混亂的「罪魁禍首」。單從語法層面來講,Webpack有點像是Lisp系語言中的宏,咱們能夠定製任何語法,可是在前端領域中這種「宏」卻被無節制地使用着,不一樣的開發人員就能定製出不一樣的類JS語法,爲了排除這種干擾,我決定直接採用了Node環境下最爲「原生」的JS寫法。
Q: 爲何沒有Webpack跟Karma的集成的相關代碼示例子?
A: Karma自己的配置並非很複雜,它只是一個測試的Runner,預編譯功能能夠依賴Webpack來完成,加入一個叫作karma-webpack做爲他們之間的橋樑便可。貼相關的代碼會致使篇幅過長,且本文重點並非「配置」。
這篇文章主要簡單介紹了一些單元測試的基本部件,每一個部件中我都列舉了JS 社區中較爲經常使用的對應的軟件庫。或許他們會是比較好的選擇但卻並非惟一的選擇。好比測試框架咱們還能夠選擇Jasmine,斷言庫咱們能夠選擇expect.js。至於選擇什麼純粹是我的喜愛的問題,在我看來區別並非很大。