測試你的前端代碼 - part2(單元測試)

本文做者:Gil Tayar

編譯:鬍子大哈 javascript

翻譯原文:blog.huziketang.com/blog/posts/…

英文鏈接:Testing Your Frontend Code: Part II (Unit Testing)html

轉載請註明出處,保留原文連接以及做者信息前端

上一篇文章《測試你的前端代碼 - part1(介紹)》中,我介紹了關於前端測試的基本知識,從本文開始將具體介紹測試技術。java

單元測試

上一節有討論過,單元測試就是以代碼單元爲單位進行測試,代碼單元能夠是一個函數,一個模塊,或者一個類。不少人認爲大多數測試都應該叫單元測試,其實個人觀點仍是那句話,無所謂怎麼叫,名字叫什麼都行。只要你作了足夠多的測試,可以保證你部署到線上的生產代碼沒有問題就能夠了。node

單元測試是最容易理解、也最容易實現的測試方式。給單元測試一個輸入,讓它自動執行,將輸出結果和預期結果作對比看其是否正確(輸入能夠是一個函數參數,輸出就是函數的返回值)。react

在寫單元測試的時候,儘可能將你的單元測試獨立出來,不要幾個單元互相引用。養成這樣良好的測試習慣。webpack

測試 Calculator 應用

第一節中提到過,爲了這系列博文,我寫了一個計算器應用,後面都會拿它進行測試。理論就講到這裏,一塊兒來看一下 Calculator 應用吧,源代碼在這裏。主要有兩個組件: keypad display ,它們自身都是 React 單元,也都沒有引用其餘單元,後面會介紹如何對它們進行測試。git

(若是你已經看了代碼可能已經發現了我沒有使用 JSX。由於我不想進行轉譯。如今 Node 和全部流行的瀏覽器都已經徹底支持 ES6 了,那麼做爲一個例子來說,讓它直接運行會更好一些。雖然它不能運行在 IE 上,不過也不要緊,若是是一個真實的線上項目,我會進行轉譯的。)github

還有一個問題是按鍵和展現的邏輯問題,必需要有代碼來控制當點擊按鍵的時候發生什麼。這裏的按鍵包括數字鍵(如「1」,「5」)和操做鍵(如「+」,「=」)。按一般的作法,我把組件設計成了展現型組件(鍵盤)和容器型組件。容器型組件在個人 App 中是惟一包含 state 的組件,它要考慮當發生按鍵行爲的時候 App 內在邏輯的問題。web

「calculator」模塊

處理邏輯問題的代碼是一個單獨的模塊——calculator。這個模塊對於單元測試是很完美的例子。由於它沒有對 I/O 和 UI 的依賴。你也應該儘可能使你的應用邏輯上保持獨立——模塊不依賴於 I/O 和 UI。

對於 Web 應用來說,I/O 是什麼?沒有文件和數據庫的操做?其實不只僅是這樣,還有 Ajax 調用,本地存儲,DOM 操做等,對我而言,任何和瀏覽器 API 有關的都是 I/O 操做。

我是怎麼把計算邏輯從 React 組件中分離出來的呢?其實很簡單,其內在邏輯是計算,我把他封裝到一個模塊中就能夠了。

這個模塊的實現也很容易——它接收一個計算器 state(一個對象)和一個字符(數字或者操做符),返回一個新的計算器 state。若是你用過 Redux,它很像 Redux 的 reducer 模式(若是你沒用過 Redux 也不要緊)。可是若是一直由上一個 state 獲取下一個 state,怎麼能回到初始狀態呢?這裏還有一個模塊叫作 initialState,經過它能夠初始化計算器。計算器的 state 並非徹底黑盒的,它包含了一個字段叫作 display,能夠把你想要展現的 state 顯示在計算器應用上。

若是你沒有耐心看源代碼的話,咱們一塊兒來看下這裏面最重要的部分,應用中算法的細節其實不重要。

module.exports.initialState = { display: '0', initial: true }

    module.exports.nextState = (calculatorState, character) => {
      if (isDigit(character)) {
        return addDigit(calculatorState, character)
      } else if (isOperator(character)) {
        return addOperator(calculatorState, character)
      } else if (isEqualSign(character)) {
        return compute(calculatorState)
      } else {
        return calculatorState
      }
    }

    //....複製代碼

再次強調這裏的實現細節並不重要,重要的是模塊的設計,它暴露出來的函數很是簡單——給一個 state,獲得下一個 state。

這就是咱們在 test-calculator 中所作的事情。那麼接下來怎麼進行測試呢?使用測試框架,目前比較流行的框架是 Mocha ,咱們就用它。不過像 Jest,Jasmine,Tape等框架也都行,隨意使用你喜歡的測試框架。

用 Mocha 進行單元測試

全部的測試框架都相似,寫測試代碼調用被測函數,經過測試框架運行他們,其中運行它們的代碼一般叫作「runner」。

Mocha runner 叫作 「mocha」,若是你看測試腳本的 package.json,能夠看到:

"scripts": {
    ...
        "test": "mocha 'test/**/test-*.js' && eslint test lib",
    ...
    },複製代碼

它會運行 test 文件夾中全部以 test- 開頭的文件,你能夠複製個人 repo,npm install 後,運行 npm test 本身試試。

(順便提一句,把全部測試都放在測試目錄,而且測試目錄放在 package 的根目錄是一個公認的 npm package 約定,若是你不想讓人以爲你不專業的話,最好仍是遵照這一約定。)

運行它,會獲得以下輸出:

這裏有 14 個測試經過的提示信息,若是沒經過,就會有紅色提示出現。

咱們看下面代碼:

const {describe, it} = require('mocha')
    const {expect} = require('chai')
    const calculator = require('../../lib/calculator')

    describe('calculator', function () {
      const stream = (characters, calculatorState = calculator.initialState) =>
        !characters
          ? calculatorState
          : stream(characters.slice(1),
                   calculator.nextState(calculatorState, characters[0]))

      it('should show initial display correctly', () => {
        expect(calculator.initialState.display).to.equal('0')
      })
      it('should replace 0 in initialState', () => {
        expect(stream('4').display).to.equal('4')
      })
    //...複製代碼

首先引入 mocha 和斷言常量 expect,這裏只引入咱們須要的函數:describeitexpect。接下來引入咱們要測試的模塊 calculator

準備開始測試,用 it 函數來表達:

it('should show initial display correctly', () => {
        expect(calculator.initialState.display).to.equal('0')
    })複製代碼

it 函數接收一個字符串(用來表示測試結果)和一個函數(待測函數)。it 測試不能單獨運行,它們必須組成一個測試組。因此如代碼中所示,用 describe 函數定義測試組,裏面包含了若干個 it 函數。

測試函數中寫什麼呢?能夠寫任何想寫的東西,在這個例子中咱們測試了初始狀態所顯示的是否是 0。若是咱們本身來實現怎麼實現呢,好比能夠像以下代碼:

if (calculator.initialState.display !== '0')
      throw 'failed'複製代碼

對於這個問題,上面代碼也是能夠測出來的。可是 expect 包含了不少特性可使測試變得更簡單,好比能夠測試數組或者對象是否和一個給定的值相等。這就是單元測試的要點,即運行一個函數,或一組函數,檢查其 運行結果 是否和 預期結果 一致。

編寫單元可測的代碼

上面的很簡單對吧!其實對於單元測試來說,難的並非單元測試自己,而是分離代碼的藝術,把代碼儘可能分離成單元可測的模塊。單元可測的代碼通常都是不依賴於其餘模塊、不依賴於 I/O 的代碼。這是比較困難的,大多數人都傾向於把邏輯代碼、I/O 代碼和 UI 代碼寫到一塊兒。困難是困難,但不是說作不到,有不少技巧可使用,好比你的代碼中有一些驗證字段,那麼你就能夠把驗證代碼組織到一塊兒造成函數,再對這個驗證函數進行測試。

測試代碼是運行在 NodeJS 下的!?

注意一個重要的事情——單元測試是在 NodeJS 下運行的!而計算器應用是運行在瀏覽器端的,上面的生產代碼都是在 NodeJS 下進行測試的,這也能夠嗎?

固然能夠。由於咱們的代碼是同構的,它能夠運行在瀏覽器端和 NodeJS 上。若是你的代碼沒有使用任何 I/O,就是說沒有對瀏覽器作任何的特化處理,那麼它就沒有理由不能運行在 NodeJS 上。另外,若是你使用了 require,它既能夠被本地的 NodeJS 識別,也能夠被像 Webpack 同樣的打包器識別。你看代碼中的 package.json,就能夠看到咱們就是使用了 Webpack,用 require 進行代碼打包:

"scripts": {
       "build": "webpack && cp public/* dist",
       ...
    }複製代碼

代碼中使用 require 來引入 React 或者其餘模塊,這不管是在 NodeJS 中仍是瀏覽器中都是通用的。

在瀏覽器中運行單元測試

咱們還可使用另外一個測試框架,Karma 。使用它能夠在瀏覽器中運行 Mocha 代碼,可是這裏表達一下個人淺見:單元測試能在 Node 下運行就在 Node 下運行,由於很容易執行和 debug(固然如今在瀏覽器中執行也很方便)。而且若是代碼不須要轉譯的話,執行的也很是快。

可是咱們的代碼沒有在瀏覽器中測試確實是個問題,由於咱們並不真正地知道代碼在瀏覽器中運行會是什麼樣子。瀏覽器中的 JS 執行環境和 NodeJS 環境可能會有微妙的差異。

總結

本文中主要介紹了什麼:

  • 介紹瞭如何使用 Mocha (和 Chai)建立單元測試;
  • 介紹了單元測試就是以代碼單元爲單位進行測試,這個代碼單元是獨立於其餘模塊的。
  • 介紹了設計模塊時應該獨立於其餘模塊。若是必定要有依賴,那麼能夠 mock 一個其餘模塊對本模塊進行單元測試,或者進行集成測試。
  • 介紹了咱們測試的代碼單元應該是同構的,這樣就能夠在 NodeJS 環境下進行測試了。
  • 介紹瞭如何寫同構代碼——沒有 I/O操做、使用 require 引入模塊、使用 Webpack 來打包模塊以使其符合瀏覽器運行環境。

下文簡介

下篇文章咱們介紹端到端測試,把咱們的代碼在真實環境(瀏覽器)中測試。請看下一篇文章《測試你的前端代碼 - part3(端到端測試)》


我最近正在寫一本《React.js 小書》,對 React.js 感興趣的童鞋,歡迎指點

相關文章
相關標籤/搜索