淺談前端單元測試

      首先聲明一點,長期以來,前端開發的單元測試並非在前端的開發過程當中所必須的,也不是每一個前端開發工程師所注意和重視的,甚至擴大到軟件開發過程當中單元測試這一環也不是在章程上有書面規定所要求的。可是隨着每一個工程的複雜化、代碼的高複用性要求和前端代碼模塊之間的高內聚低耦合的需求,前端工程中的單元測試流程就顯得頗有其必要。javascript

1.前端單元測試是什麼

      首先咱們要明確測試是什麼:html

       爲檢測特定的目標是否符合標準而採用專用的工具或者方法進行驗證,並最終得出特定的結果。

       對於前端開發過程來講,這裏的特定目標就是指咱們寫的代碼,而工具就是咱們須要用到的測試框架(庫)、測試用例等。檢測處的結果就是展現測試是否經過或者給出測試報告,這樣才能方便問題的排查和後期的修正。前端

       基於測試「是什麼」的說法,爲便於剛從事前端開發的同行的進階理解,那咱們就列出單元測試它「不是什麼」:java

須要訪問數據庫的測試不是單元測試node

須要訪問網絡的測試不是單元測試git

須要訪問文件系統的測試不是單元測試github

--- 修改代碼的藝術面試

對於單元測試「不是什麼」的引用解釋,至此點到爲止。鑑於篇幅限制,對於引用內容,我想前端開發的同行們看到後會初步有一個屬於本身的理解。chrome

2.單元測試的意義以及爲何須要單元測試

2.1   單元測試的意義數據庫

      對於如今的前端工程,一個標準完整的項目,測試是很是有必要的。不少時候咱們只是完成了項目而忽略了項目測試的部分,測試的意義主要在於下面幾點:

  1. TDD(測試驅動開發) 被證實是有效的軟件編寫原則,它能覆蓋更多的功能接口。
  2. 快速反饋你的功能輸出,驗證你的想法。
  3. 保證代碼重構的安全性,沒有一成不變的代碼,測試用例能給你多變的代碼結構一個定心丸。
  4. 易於測試的代碼,說明是一個好的設計。作單元測試以前,確定要實例化一個東西,假如這個東西有不少依賴的話,這個測試構7. 造過程將會很是耗時,會影響你的測試效率,怎麼辦呢?要依賴分離,一個類儘可能保證功能單一,好比視圖與功能分離,這樣的話,你的代碼也便於維護和理解。

2.2   爲何須要單元測試

  1. 首先是一個前端單元測試的根本性起因:JavaScript 是動態語言,缺乏類型檢查,編譯期間沒法定位到錯誤; JavaScript 宿主的兼容性問題。好比 DOM 操做在不一樣瀏覽器上的表現。
  2. 正確性:測試能夠驗證代碼的正確性,在上線前作到內心有底。
  3. 自動化:固然手工也能夠測試,經過console能夠打印出內部信息,可是這是一次性的事情,下次測試還須要從頭來過,效率不能獲得保證。經過編寫測試用例,能夠作到一次編寫,屢次運行。
  4. 解釋性:測試用例用於測試接口、模塊的重要性,那麼在測試用例中就會涉及如何使用這些API。其餘開發人員若是要使用這些API,那閱讀測試用例是一種很好地途徑,有時比文檔說明更清晰。
  5. 驅動開發,指導設計:代碼被測試的前提是代碼自己的可測試性,那麼要保證代碼的可測試性,就須要在開發中注意API的設計,TDD將測試前移就是起到這麼一個做用。
  6. 保證重構:互聯網行業產品迭代速度很快,迭代後必然存在代碼重構的過程,那怎麼才能保證重構後代碼的質量呢?有測試用例作後盾,就能夠大膽的進行重構。

3.如何寫單元測試用例

3.1 原則

  • 測試代碼時,只考慮測試,不考慮內部實現
  • 數據儘可能模擬現實,越靠近現實越好
  • 充分考慮數據的邊界條件
  • 對重點、複雜、核心代碼,重點測試
  • 利用AOP(beforeEach、afterEach),減小測試代碼數量,避免無用功能
  • 測試、功能開發相結合,有利於設計和代碼重構

3.2 兩個經常使用的單元測試方法論

   在單元測試中,經常使用的方法論有兩個:TDD(測試驅動開發)&BDD(行爲驅動開發)

   對於以前沒據說過前端測試這兩個模式的同行能夠在此瞭解一下,篇幅限制此處再也不敖述。

3.3 相信你看完以後也有一個本身對TDD和BDD的我的觀點,在此我先談談我對TDD和BDD的 理解:

TDD(Test-driven development)

其基本思路是經過測試來推進整個開發的進行。

  • 單元測試的首要目的不是爲了可以編寫出大覆蓋率的所有經過的測試代碼,而是須要從使用者(調用者)的角度出發,嘗試函數邏輯的各類可能性,進而輔助性加強代碼質量

  • 測試是手段而不是目的。測試的主要目的不是證實代碼正確,而是幫助發現錯誤,包括低級的錯誤

  • 測試要快。快速運行、快速編寫

  • 測試代碼保持簡潔

  • 不會忽略失敗的測試。一旦團隊開始接受1個測試的構建失敗,那麼他們漸漸地適應二、三、4或者更多的失敗。在這種狀況下,測試集就再也不起做用

須要注意的是

  • 必定不能誤解了TDD的核心目的!

  • 測試不是爲了覆蓋率和正確率

  • 而是做爲實例,告訴開發人員要編寫什麼代碼

  • 紅燈(代碼還不完善,測試掛)-> 綠燈(編寫代碼,測試經過)-> 重構(優化代碼並保證測試經過)

TDD的過程是

  1. 需求分析,思考實現。考慮如何「使用」產品代碼,是一個實例方法仍是一個類方法,是從構造函數傳參仍是從方法調用傳參,方法的命名,返回值等。這時其實就是在作設計,並且設計以代碼來體現。此時測試爲紅

  2. 實現代碼讓測試爲」綠燈「

  3. 重構,而後重複測試

  4. 最終符合全部要求即:

    • 每一個概念都被清晰的表達

    • 代碼中無自我重複

    • 沒有多餘的東西

    • 經過測試

BDD(Behavior-driven development):

行爲驅動開發(BDD),重點是經過與利益相關者(簡單說就是客戶)的討論,取得對預期的軟件行爲的認識,其重點在於溝通

BDD過程是:

  1. 從業務的角度定義具體的,以及可衡量的目標

  2. 找到一種能夠達到設定目標的、對業務最重要的那些功能的方法

  3. 而後像故事同樣描述出一個個具體可執行的行爲。其描述方法基於一些通用詞彙,這些詞彙具備準確無誤的表達能力和一致的含義。例如,expect, should, assert

  4. 尋找合適語言及方法,對行爲進行實現

  5. 測試人員檢驗產品運行結果是否符合預期行爲。最大程度的交付出符合用戶指望的產品,避免表達不一致帶來的問題

4. Mocha/Karma+Travis.CI的前端測試工做流

      以上內容從什麼是單元測試談到單元測試的方法論。那麼怎樣用經常使用框架進行單元測試?單元測試的工具環境是什麼?單元測試的實際示例是怎樣的?

      首先應該簡單介紹一下Mocha、Karma和Travis.CI

Mocha:mocha 是一個功能豐富的前端測試框架。所謂"測試框架",就是運行測試的工具。經過它,能夠爲JavaScript應用添加測試,從而保證代碼的質量。mocha 既能夠基於 Node.js 環境運行 也能夠在瀏覽器環境運行。欲瞭解更多可去官方網站進行學習。其官方介紹爲:

Mocha is a feature-rich JavaScript test framework running on Node.js and in the browser, making asynchronous testing simple and fun. Mocha tests run serially, allowing for flexible and accurate reporting, while mapping uncaught exceptions to the correct test cases. Hosted on GitHub.

Karma:一個基於Node.js的JavaScript測試執行過程管理工具(Test Runner)。該工具可用於測試全部主流Web瀏覽器,也可集成到CI(Continuous integration)工具,也可和其餘代碼編輯器一塊兒使用。這個測試工具的一個強大特性就是,它能夠監控文件的變化,而後自行執行,經過console.log顯示測試結果。Karma的一個強大特性就是,它能夠監控一套文件的變換,並當即開始測試已保存的文件,用戶無需離開文本編輯器。測試結果一般顯示在命令行中,而非代碼編輯器。這也就讓 Karma 基本能夠和任何 JS 編輯器一塊兒使用。

Travis.CI: 提供的是持續集成服務(Continuous Integration,簡稱 CI)。它綁定 Github 上面的項目,只要有新的代碼,就會自動抓取。而後,提供一個運行環境,執行測試,完成構建,還能部署到服務器。

持續集成指的是隻要代碼有變動,就自動運行構建和測試,反饋運行結果。確保符合預期之後,再將新代碼"集成"到主幹。

持續集成的好處在於,每次代碼的小幅變動,就能看到運行結果,從而不斷累積小的變動,而不是在開發週期結束時,一會兒合併一大塊代碼。

對於Travis.CI,建議移步到阮大大廖大大的我的網站上學習,兩位老師講的要比我在這兒寫的更清晰。

斷言庫

     基本工具框架介紹完畢後,相信稍微瞭解點測試的同行都知道,作單元測試是須要寫測試腳本的,那麼測試腳本就須要用到斷言庫。」斷言「,我的理解即爲」用彼代碼判定測試此代碼的正確性,檢驗並暴露此代碼的錯誤。「那麼對於前端單元測試來講,有如下經常使用斷言庫:

看一段代碼示例:

expect(add(1, 1)).to.be.equal(2);

這是一句斷言代碼。

所謂"斷言",就是判斷源碼的實際執行結果與預期結果是否一致,若是不一致就拋出一個錯誤。上面這句斷言的意思是,調用 add(1, 1),結果應該等於 2。全部的測試用例(it 塊)都應該含有一句或多句的斷言。它是編寫測試用例的關鍵。斷言功能由斷言庫來實現,Mocha 自己不帶斷言庫,因此必須先引入斷言庫。

引入斷言庫代碼示例:

var expect = require('chai').expect;

斷言庫有不少種,Mocha 並不限制使用哪種,它容許你使用你想要的任何斷言庫。上面代碼引入的斷言庫是 chai,而且指定使用它的 expect 斷言風格。下面這些常見的斷言庫:

此處主要介紹一下node assert中經常使用的API

  • assert(value[, message])
  • assert.ok(value[, message])
  • assert.equal(actual, expect[, message])
  • assert.notEqual(actual, expected[, message])
  • assert.strictEqual(actual, expect[, message])
  • assert.notStrictEqual(actial, expected[, message])
  • assert.deepEqual(actual, expect[, message])
  • assert.notDeepEqual(actual, expected[, message])
  • assert.deepStrictEqual(actual, expect[, message])
  • assert.notDeepStrictEqual(actual, expected[, message])
  • assert.throws(block[, error][, message])
  • assert.doesNotThrow(block[, error][, message])

assert(value[, message])

斷言 value 的值是否爲true,這裏的等於判斷使用的是 == 而不是 ===。message 是斷言描述,爲可選參數。

const assert = require('assert');
assert(true);
複製代碼

assert.ok(value[, message])

使用方法同 assert(value[, message])

assert.equal(actual, expect[, message])

預期 actual 與 expect值相等。equal用於比較的 actual 和 expect 是基礎類型(string, number, boolearn, null, undefined)的數據。其中的比較使用的是 == 而不是 ===。

it('assert.equal', () => {
  assert.equal(null, false, 'null compare with false');  // 報錯
  assert.equal(null, true, 'null compare with true');  // 報錯
  assert.equal(undefined, false, 'undefined compare with false'); // 報錯
  assert.equal(undefined, true, 'undefined compare with true'); // 報錯
  assert.equal('', false, '"" compare with false');  // 正常
})
複製代碼

notEqual(actual, expected[, message])

用法同 assert.equal(actual, expect[, message]) 只是對預期結果取反(即不等於)。

assert.strictEqual(actual, expect[, message])

用法同 assert.equal(actual, expect[, message]) 可是內部比較是使用的是 === 而不是 ==。

assert.notStrictEqual(actial, expected[, message])

用法同 assert.strictEqual(actual, expect[, message]) 只是對預期結果取反(即不嚴格等於)。

it('assert.strictEqual', () => {
  assert.strictEqual('', false); // 報錯
})
複製代碼

assert.deepEqual(actual, expect[, message])

deepEqual 方法用於比較兩個對象。比較的過程是比較兩個對象的 key 和 value 值是否相同, 比較時用的是 == 而不是 ===。

it('assert.deepEqual', () => {
  const a = { v: 'value' };
  const b = { v: 'value' };
  assert.deepEqual(a, b);
})
複製代碼

assert.notDeepEqual(actual, expected[, message])

用法同 assert.deepEqual(actual, expect[, message]) 只是對預期結果取反(即不嚴格深等於)。

assert.deepStrictEqual(actual, expect[, message])

用法同 assert.deepEqual(actual, expect[, message]) 可是內部比較是使用的是 === 而不是 ==。

assert.notDeepStrictEqual(actual, expected[, message])

用法同 assert.deepStrictEqual(actual, expect[, message]) 只是對結果取反(即不嚴格深等於)。

assert.throws(block[, error][, message])

錯誤斷言與捕獲, 斷言指定代碼塊運行必定會報錯或拋出錯誤。若代碼運行未出現錯誤則會斷言失敗,斷言異常。

it('throws', () => {
  var fun = function() {
    xxx
  };
  assert.throws(fun, 'fun error');
})
複製代碼

assert.doesNotThrow(block[, error][, message])

錯誤斷言與捕獲, 用法同 throws 相似,只是和 throws 預期結果相反。斷言指定代碼塊運行必定不會報錯或拋出錯誤。若代碼運行出現錯誤則會斷言失敗,斷言異常。

it('throws', () => {
  var fun = function() {
    xxx
  };
  assert.doesNotThrow(fun, 'fun error');
})複製代碼


相應的工具介紹以後,針對Mocha、Karma以及Travis.CI的用法談點我的在操做時的實踐經驗。

Mocha

  • 安裝mocha
npm install mocha -g
複製代碼

固然也能夠在不在全局安裝,只安局部安裝在項目中

npm install mocha --save
複製代碼
  • 建立一個測試文件 test.js
var assert = require('assert')

describe('Array', function() {
  describe('#indexOf()', function() {
    it('should return -1 when the value is not present', function() {
      assert.equal(-1, [1, 2, 3].indexOf(-1))
    })
  })
})
複製代碼

這段文件和簡單就是測試 Array 的一個 indexOf() 方法。這裏我是用的斷言庫是 Node 所提供的 Assert 模塊裏的API。這裏斷言 -1 等於 數組 [1, 2, 3] 執行 indexOf(-1)後返回的值,若是測試經過則不會報錯,若是有誤就會報出錯誤。

下面咱們使用全局安裝的 mocha 來運行一下這個文件 mocha test.js
下面是返回結果


基礎測試用例實例

const assert = require('assert');

describe('測試套件描述', function() {
  it('測試用例描述: 1 + 2 = 3', function() {
    // 測試代碼
    const result = 1 + 2;
    // 測試斷言
    assert.equal(result, 3);
  });
});
複製代碼

Mocha 測試用例主要包含下面幾部分:

  1. describe 定義的測試套件(test suite)
  2. it 定義的測試用例(test case)
  3. 測試代碼
  4. 斷言部分

說明:每一個測試文件中能夠有多個測試套件和測試用例。mocha不只能夠在node環境運行, 也能夠在瀏覽器環境運行;在node中運行也能夠經過npm i mocha -g全局安裝mocha而後以命令行的方式運行測試用例也是可行的。

這裏略微詳細介紹下測試腳本寫法

Mocha 的做用是運行測試腳本,首先必須學會寫測試腳本。所謂"測試腳本",就是用來測試源碼的腳本。下面是一個加法模塊 add.js 的代碼。

// add.js
function add(x, y) {
  return x + y;
}

module.exports = add;
複製代碼

要測試這個加法模塊是否正確,就要寫測試腳本。一般,測試腳本與所要測試的源碼腳本同名,可是後綴名爲.test.js(表示測試)或者.spec.js(表示規格)。好比,add.js 的測試腳本名字就是 add.test.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),表示一組相關的測試。它是一個函數,第一個參數是測試套件的名稱("加法函數的測試"),第二個參數是一個實際執行的函數。

it 塊稱爲"測試用例"(test case),表示一個單獨的測試,是測試的最小單位。它也是一個函數,第一個參數是測試用例的名稱("1 加 1 應該等於 2"),第二個參數是一個實際執行的函數。


expect 斷言的優勢是很接近天然語言,下面是一些例子。

// 相等或不相等
expect(4 + 5).to.be.equal(9);
expect(4 + 5).to.be.not.equal(10);
expect(foo).to.be.deep.equal({ bar: 'baz' });

// 布爾值爲true
expect('everthing').to.be.ok;
expect(false).to.not.be.ok;

// typeof
expect('test').to.be.a('string');
expect({ foo: 'bar' }).to.be.an('object');
expect(foo).to.be.an.instanceof(Foo);

// include
expect([1, 2, 3]).to.include(2);
expect('foobar').to.contain('foo');
expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo');

// empty
expect([]).to.be.empty;
expect('').to.be.empty;
expect({}).to.be.empty;

// match
expect('foobar').to.match(/^foo/);
複製代碼

基本上,expect 斷言的寫法都是同樣的。頭部是 expect 方法,尾部是斷言方法,好比 equal、a/an、ok、match 等。二者之間使用 to 或 to.be 鏈接。若是 expect 斷言不成立,就會拋出一個錯誤。事實上,只要不拋出錯誤,測試用例就算經過。

it('1 加 1 應該等於 2', function() {});
複製代碼

上面的這個測試用例,內部沒有任何代碼,因爲沒有拋出了錯誤,因此仍是會經過。


Karma

基於 karma 測試經常使用的一些模塊

模塊安裝

# 基礎測試庫
npm install karma-cli -g
npm install karma mocha karma-mocha --save-dev

# 斷言庫
npm install should --save-dev
npm install karma-chai --save-dev

# 瀏覽器相關
npm install karma-firefox-launcher --save-dev
npm install karma-chrome-launcher --save-dev
複製代碼

配置

這裏的配置主要關注的是karma.conf.js的相關配置。若是要使用 karma 和 mocha 最好經過npm install karma-cli -g全局安裝karma-cli具體配置配置說明

須要注意的兩個字段:

  • singleRun: 若是值爲 true, 則在瀏覽器運行完測試後會自動退出關閉瀏覽器窗口。singleRun的值咱們能夠更具運行環境來動態賦值, 能夠啓動命令中添加NODE_ENV變量。
  • browsers: 瀏覽器配置(能夠配置多個瀏覽器); 若是瀏覽器沒法啓動須要進行相關瀏覽器的配置。設置自啓動瀏覽器時候若是瀏覽器啓動失敗可能須要設置爲--no-sandbox模式。
{
  "browsers": ["Chrome", "ChromeHeadless", "ChromeHeadlessNoSandbox"],
  "customLaunchers": {
    "ChromeHeadlessNoSandbox": {
      "base": "ChromeHeadless",
      "flags": ["--no-sandbox"]
    }
  }
}
複製代碼

或者

{
  "browsers": ["Chrome_travis_ci"],
  "customLaunchers": {
    "Chrome_travis_ci": {
      "base": "Chrome",
      "flags": ["--no-sandbox"]
    }
  }
}複製代碼


Github項目接入Travis.CI進行集成自動化測試的步驟

  • 在github建立並完成一個能夠待測試的項目。這裏的完成是指須要完成基本的項目功能,和測試用例代碼。
  • 配置travis-ci能識別讀取的配置文件,這樣travis-ci接入的時候纔可以知道測試時的一些配置。
  • github 和 travis-ci 是個站點,換句話說就是兩個東西若是能打通呢。須要用戶登陸 travis-ci 並受權訪問到你的 github 項目並進行相關的項目設置。
  • 接入完成後就能夠根據本身的須要來運行寫好的測試代碼,也能夠設置按期任務去跑測試。

  • 項目建立、完善項目功能和測試代碼。

    • 項目需求: 實現一個求和方法
    • 測試: 經過 mocha 來測試完成的求和方法。

    下面是項目結構,項目建立完成後經過 npm i mocha -D 安裝 mocha 模塊。而後在本地運行 npm test 看是否可以測試經過。若是可以測試經過則說明咱們的能夠繼續下一步了。


    建立 travis-ci 測試配置文件

    建立 travis-ci 配置文件 .travis.yml, 文件內容。更多關於配置文件的說明在travis官網可查詢

    language: node_js
    node_js:
      - "node"
      - "8.9.4"
    複製代碼

    至此基本完成了項目開發和測試代碼編寫的過程,下一步就能夠接入 travis-ci 測試了。

    接入 travis-ci

    經過GitHub登陸 travis-ci 的官網 www.travis-ci.org/


    找到GitHub上剛纔建立的須要測試的項目,並開啓測試


    查看測試過程,及時發現問題。


    查看測試狀態是否經過測試,若是未經過及時排查問題反覆修改;若是經過能夠在項目文檔中添加一個測試經過的標識。

    總結

    年初的時候我曾去面試一個公司,公司是一個創業公司,老闆是前百度首席架構師林仕鼎先生,公司名字叫愛雲校,主要業務是爲基礎教育提供數據服務,有本身的平臺和產品。當天面試官問個人面試問題中最令我印象深入的就是:之前你作前端的時候的單元測試所用的框架和工做流是什麼?當時說實話我很懵逼,懵逼到我甚至不知道在前端領域什麼是單元測試?要測試什麼?用的啥工具?一時語塞,我說之前沒有用過,也沒有作過前端測試。當時面試官老哥明顯臉上有些失望,可是最終的結果是公司錄用了我,我說出這個經歷的緣由,其實想傳達的意思是:如今的前端開發早已不是早年的切圖、特效實現和視覺表現。如今的前端開發工程師更多的是你做爲一個在整個項目產品開發團隊中理論上(其實也是事實上)離用戶最近的一個崗位,應不只僅限於實現純傳統前端的功能,更須要基於明白客戶需求這個基礎上統籌好前端和後端的良好耦合,以及前端功能的準確無誤。根據本身的面試求職經歷,今天在這裏談到的前端單元測試的內容,我的認爲在某些公司項目中,可能並非必須的,但做爲發展最快的IT領域之一,前端工程師掌握前端單元測試在將來時間只能是要求愈來愈硬性,而不是一直停留在無關緊要了解範疇,由於這跟前端開發的發展趨勢息息相關。

    可是同時,在我我的觀點範疇內,至少目前我仍是堅持開發爲主測試爲輔的流程,對於像TDD這種單元測試指導開發流程,目前並不推崇。我的認爲,這是一個頗有創新性的方法論,也並非如今徹底不可行,我的認爲只是可行的範疇還不夠寬,可行的條件要求還很嚴苛。因此相對於TDD,測試主導開發,對於目前準備進階的前端開發者,我的更建議,瞭解某種之後會使用的新趨勢和技術是有必要的,但做爲技術人應該在學習新的、前衛的技術的同時不可迷失自我一味追求新技術,更重要的是要磨練當下的主流技能。相比於將來的單元測試主導開發流程,倒不如在目前這個時間節點精進基礎開發流程,好比讓本身的JS代碼更專一於模塊化和功能化的實現,這樣的同時也會讓單元測試更有效率,真正發揮目前單元測試對前端工程化的做用。

    相關文章
    相關標籤/搜索