單元測試整理

學習單元測試的時候接觸了不少概念karma、mocha、Jesmine、chai、expect、assert、should、sinon等,容易混亂,在此作個梳理。javascript

1. 測試框架 Mocha、Jesmine

1.1 Mocha

入門文章參考:www.ruanyifeng.com/blog/2015/1…html

Mocha是一個經常使用的JS測試框架,能夠在瀏覽器和Nodejs環境使用。Mocha不帶斷言須要和斷言庫結合使用。項目中使用的也是Mocha+chai+sinon的結合。例如給ndfront組件寫的單元測試,詳情查看github倉庫:github.com/baihexx/ndf…前端

特色:靈活,可擴展性好,可配合不一樣的斷言庫使用,可是自身集成度不高java

1.2 Jesmine

Jesmine也是經常使用的測試框架,項目中沒有用這個。node

特色:內置斷言庫,集成度高,方便支持異步測試,可是靈活性差,斷言風格單一git

2. 斷言庫

2.1 assert

assert模塊是Node的內置模塊,用於斷言。
官方API
經常使用APIes6

eg:github

var assert = requier('assert')
describe('desc1', function() {
    it('desc2', function() {
        assert(a === 1, '預期a的值是1')
    })
})
複製代碼

2.2 should.js

github 倉庫
API docsajax

should.js是個第三方斷言庫,常和Mocha聯合使用。後端

  • 使用方法1:
    requier('should'): 擴展Object.prototype,增長should屬性,全部Object能夠直接獲取should使用,eg:
var should = require('should');
(5).should.be.exactly(5).and.be.a.Number();

var a = null
a.should.not.be.ok() // 報錯
複製代碼
  • 使用方法2:
    如果undefined或者null,並無繼承Object的原型鏈,沒有should屬性可用,可採用以下方法:
var should = require('should/as-function');
var a = null
should(a).not.be.ok() // pass
should(10).be.exactly(5).and.be.a.Number();
複製代碼

2.3 Chai

官網API: 安裝方法等查看官網

Chai是個斷言庫,常和Mocha結合使用。他有多種斷言風格(assertion style):assert, expect, should

  • assert斷言風格:和nodejs的assert模塊相似(多了寫語法糖),是一種非鏈式語言風格 eg:
var assert = requier('chai').assert
assert.notEqual(3, 4, 'these numbers are not equal')
複製代碼
  • expect斷言風格:expect和should都是BDD風格,是一種鏈式語言風格, 鏈接詞有 to,be,been,is等天然語言, eg:
var expect = requier('Chai').expect
expect([1, 2, 3]).to.be.an('array').that.includes(2)
複製代碼
  • should斷言風格:should()擴展了Object.prototype,增長了should屬性,使用方法以下。eg:
var should = require('chai').should() //actually call the function
var foo = 'bar'
foo.should.be.a('string')
foo.should.equal('bar')
foo.should.have.lengthOf(3)
複製代碼

(注意should對IE兼容性很差)

3. 測試運行工具:karma

Github 倉庫

定義:A simple tool that allows you to execute JavaScript code in multiple real browsers. The main purpose of Karma is to make your test-driven development easy, fast, and fun.

Karma不是測試框架,也不是斷言庫,他會開啓一個HTTP服務,將測試文件生成一個Html文件,在瀏覽器內運行、調試。Karma不指定測試框架,經過插件和Mocha、Jesmine、QUnit均可以結合使用。

配置項較多,根據官網說明配置,並不難。

測試覆蓋率:根據提示安裝、配置便可生成覆蓋率報告

4. 測試輔助工具:Sinon

Github 倉庫
官網
入門參考(侵刪)

爲何須要Sinon?在作單元測試的時候,咱們會發現咱們要測試的方法會引用不少外部依賴的對象,好比:(發送郵件,網絡通信,記錄Log, 文件系統之類的),而咱們無法控制這些外部依賴的對象。例如:前端項目一般是用Ajax去服務端請求數據,獲得數據以後作進一步的處理。可是作單元測試的時候一般不真的去服務端請求數據,不只麻煩,可能服務端接口還沒作好,這種不肯定的依賴使得測試變得複雜。因此咱們須要模擬這個請求數據的過程,Sinon用來解決這個問題。

Sinon的工做本質是「測試替身」,測試替身用來替換測試中的部分代碼,使得測試複雜代碼變得簡單。

Sinon提供了三個功能:示例講解看入門文章,再也不贅述

  • spy(間諜):提供函數調用的信息,但不會改變函數的行爲
  • stub:與spies相似,可是會徹底替換目標函數。這使得一個被stubbed的函數能夠作任何你想要的 —— 例如拋出一個異常,返回某個特定值等等。
  • mock:經過組合spies和stubs,使替換一個完整對象更容易

eg:admin/misc/user.js: user.getUser() (看不懂的隨便看看,這是實際的項目代碼單元測試)
user.getUser函數用來獲取用戶信息,用戶輸入工號後向服務端請求數據,咱們的測試用例重點在前端代碼,不該依賴服務端纔可測試,so 應該模擬ajax請求。
(1)nd-spa中ajax.js請求代碼以下:實際的請求函數爲:request.get, so應該mock request的get函數

(2)測試用例代碼以下:

  • sinon.stub(obj, functionname, mockFun)
  • ajax.js中的請求代碼調用的set(),send(),end()函數實際是mock中定義的函數,並無作實際的後端請求,end的callback直接返回數據
  • 注意L11:設置函數最大時長,寫成function(done)形式纔可用this.timeout,不然es6的箭頭函數中this是window

5. 多步驟測試用例(實際項目記錄,可不看,估計看不明白)

用戶在前端頁面中一般是經過點擊鼠標期待某種效果,這個過程一般不作單元測試,由於複雜度較高,且頁面變更較快,性價比很低。可是項目中某些公共的業務組件,需求變更小,步驟相對簡單,但使用又很是多,例如social管理後臺中的搜索用戶admin/misc/user.js:user.autoComplete(),完整的過程是:用戶輸入用戶信息,而後選擇搜索到的匹配用戶,再點擊搜索到的用戶,該輸入框的值變爲選擇的用戶。這個過程如何編寫單元測試。
這裏涉及到多步驟的模擬。

(1)util.js 封裝多步執行函數

// utils.js
export function triggerHTMLEvents (target, event, process) {
  const e = document.createEvent('HTMLEvents')
  e.initEvent(event, true, true)
  if (process) process(e)
  target.dispatchEvent(e)
  return e
}

export function triggerMouseEvents (target, event, process) {
  const e = document.createEvent('MouseEvents')
  e.initEvent(event, true, true)
  if (process) process(e)
  target.dispatchEvent(e)
  return e
}

export function triggerUIEvents (target, event, process) {
  const e = document.createEvent('UIEvents')
  e.initEvent(event, true, true)
  if (process) process(e)
  target.dispatchEvent(e)
  return e
}

// timeout: 執行間隔可設置
function createStep ({ step, timeout }) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      try {
        resolve(step())
      } catch (err) {
        reject(err)
      }
    }, timeout || 0)
  })
}
// 多步驟執行函數, 執行步驟封裝在arr數組中
export function runSteps (arr) {
  if (arr.length === 0) {
    return
  }
  let firstStep = createStep(arr[0])
  const others = arr.splice(1)
  others.forEach(item => {
    firstStep = firstStep.then(() => {
      return createStep(item)
    })
  })
}
複製代碼

(2)使用

L116:給input設置值
L117:觸發input的change事件,執行nd-autocomplete/src/input.js中change事件,如何調用的看autocomplete組件 L122:點擊搜索列表的item,將值設置到input,而後驗證input的值

過程當中遇到一個額外的問題,在此記錄下,備忘
(1)user中getUsers函數用到ucOrgId,查看代碼(var ucOrgId = auth.getAuth('uc_org_id') 且僅僅在登陸代碼中有auth.setAuth())可知該組織id信息必須有,可是測試頁面中沒有登陸,獲取不到該信息,so,造登陸數據,並設置到auth中。

(2)造登陸數據並設置到auth中時遇到一個問題:若在user.spec.js中 引入auth,then auth.setAuth(...), 結果不對
緣由:user.js中在開頭就執行了獲取ucOrgId的函數,咱們在測試代碼中先引入auth和user,這時ucOrgId已經獲取了,且是空值,即便假造登陸數據的函數寫在import user以前也是沒用,由於es6有提高的功能,老是先執行import,致使函數執行在後面,解決方法是:將設置登陸數據的函數寫在單獨的文件中,使用import的方法,且在user以前inport,就會達到先執行設置登陸數據的效果。(具體看代碼更清晰)
(import的提高再複習下)

Jest TODO

相關文章
相關標籤/搜索