【Node Hero】9. Node.js 單元測試

本文轉載自:衆成翻譯
譯者:網絡埋伏紀事
連接:http://www.zcfy.cc/article/1754
原文:https://blog.risingstack.com/node-hero-node-js-unit-testing-tutorial/node

本教程將會學習 Node.js 中的單元測試是什麼,以及如何正確地測試你的應用程序。git

測試 Node.js 應用程序

你能夠把測試看成你建立的應用程序的保障措施。他們將不只運行在你的本機上,還會在 CI 服務上,這樣失敗的構建就不會推送到產品系統中。github

你也許會問:個人應用程序中該測試什麼?我應該有多少測試?web

答案因情而異,可是根據經驗,你能夠遵循測試金字塔制定的準則。npm

Test Pyramid for Node.js Unit Testing

基本上,測試金字塔描述你應該編寫單元測試集成測試端到端測試。集成測試要比端到端測試多,單元測試甚至要更多一些。json

下面咱們來看看如何爲應用程序添加單元測試!網絡

請注意,這裏咱們不打算討論集成測試和端到端測試,由於它們遠遠超出了本教程的範疇。app

    • *函數

Node.js 應用程序單元測試

編寫單元測試,是爲了看看給定的模塊(單元)是否工做。全部依賴都被剔除了,意味着咱們要爲模塊提供僞依賴。單元測試

應該爲指定模塊暴露的方法,而不是內部操做提供測試。

單元測試剖析

每一個單元測試有以下結構:

  1. 測試設置

  2. 調用被測試的方法

  3. 斷言

每一個單元測試應該只測試一個關注點(固然,這不意味着你能夠只添加一個斷言)

用於 Node.js 單元測試的模塊

對於單元測試,咱們打算用以下模塊:

  • 測試運行器: mocha,或者 tape

  • 斷言庫: chai, 或者 assert 模塊 (用於斷言)

  • 測試 spy、stub 以及 mock: sinon (用於測試設置)

Spy、stub 和 mock - 用哪個以及何時用?

在動手寫單元測試以前,咱們先看看什麼是 spy、stub 和 mock!

Spy

可使用 spy 來獲取函數調用上的信息,好比函數被調用了多少次,或者傳遞了什麼參數給它們。

it('calls subscribers on publish', function () {  
  var callback = sinon.spy()
  PubSub.subscribe('message', callback)

  PubSub.publishSync('message')

  assertTrue(callback.called)
})
// 採用的示例來自於 sinon 文檔網站: http://sinonjs.org/docs/
Stub

Stub(樁)與 spy 相似,可是它是替換目標函數。可使用 stub 來控制一個方法的行爲,從而強制一個代碼路徑(好比拋出異常),或者阻止對外部資源的調用(好比 HTTP API)。

it('calls all subscribers, even if there are exceptions', function (){  
  var message = 'an example message'
  var error = 'an example error message'
  var stub = sinon.stub().throws()
  var spy1 = sinon.spy()
  var spy2 = sinon.spy()

  PubSub.subscribe(message, stub)
  PubSub.subscribe(message, spy1)
  PubSub.subscribe(message, spy2)

  PubSub.publishSync(message, undefined)

  assert(spy1.called)
  assert(spy2.called)
  assert(stub.calledBefore(spy1))
})
// 採用的示例來自於 sinon 文檔網站: http://sinonjs.org/docs/
Mock

mock 是帶有預先編好的行爲和指望值的僞方法。

it('calls all subscribers when exceptions happen', function () {  
  var myAPI = { 
    method: function () {} 
  }

  var spy = sinon.spy()
  var mock = sinon.mock(myAPI)
  mock.expects("method").once().throws()

  PubSub.subscribe("message", myAPI.method)
  PubSub.subscribe("message", spy)
  PubSub.publishSync("message", undefined)

  mock.verify()
  assert(spy.calledOnce)
// 採用的示例來自於 sinon 文檔網站: http://sinonjs.org/docs/
})

如你所見,對於 mock,你必須預先定義好指望的值。

    • *

假設要測試以下的模塊:

const fs = require('fs')  
const request = require('request')

function saveWebpage (url, filePath) {  
  return getWebpage(url, filePath)
    .then(writeFile)
}

function getWebpage (url) {  
  return new Promise (function (resolve, reject) {
    request.get(url, function (err, response, body) {
      if (err) {
        return reject(err)
      }

      resolve(body)
    })
  })
}

function writeFile (fileContent) {  
  let filePath = 'page'
  return new Promise (function (resolve, reject) {
    fs.writeFile(filePath, fileContent, function (err) {
      if (err) {
        return reject(err)
      }

      resolve(filePath)
    })
  })
}

module.exports = {  
  saveWebpage
}

這個模塊作一件事情:將網頁(基於指定的 URL)保存爲本機上的一個文件。要測試該模塊,咱們必須拔掉 fs 模塊和 request 模塊。

在咱們 RisingStack 團隊中,在真正開始爲本模塊編寫單元測試前,咱們一般添加一個 test-setup.spec.js 文件來作基礎測試設置,好比建立 sinon 沙箱。這樣能夠省下每次測試後編寫 sinon.sandbox.create()sinon.sandbox.restore()

// test-setup.spec.js
const sinon = require('sinon')  
const chai = require('chai')

beforeEach(function () {  
  this.sandbox = sinon.sandbox.create()
})

afterEach(function () {  
  this.sandbox.restore()
})

此外,請注意,咱們老是將測試文件放在挨着實現文件的地方,因此就有了 .spec.js 這個名稱。在咱們的 package.json 文件中,能夠找到這些行:

{
  "test-unit": "NODE_ENV=test mocha '/**/*.spec.js'",
}

有了這些設置後,就能夠寫測試自己了!

const fs = require('fs')  
const request = require('request')

const expect = require('chai').expect

const webpage = require('./webpage')

describe('The webpage module', function () {  
  it('saves the content', function * () {
    const url = 'google.com'
    const content = '<h1>title</h1>'
    const writeFileStub = this.sandbox.stub(fs, 'writeFile', function (filePath, fileContent, cb) {
      cb(null)
    })

    const requestStub = this.sandbox.stub(request, 'get', function (url, cb) {
      cb(null, null, content)
    })

    const result = yield webpage.saveWebpage(url)

    expect(writeFileStub).to.be.calledWith()
    expect(requestStub).to.be.calledWith(url)
    expect(result).to.eql('page')
  })
})

完整的代碼庫在這裏找到:https://github.com/RisingStack/nodehero-testing

代碼覆蓋率

要了解你的代碼庫被測試覆蓋的狀況,你能夠生成一個覆蓋率報告。

這個報告將包含以下指標:

  • 覆蓋率

  • 語句覆蓋率

  • 分支覆蓋率

  • 函數覆蓋率

在 RisingStack 公司中,咱們使用 istanbul 計算代碼覆蓋率。你應該將以下腳本添加到 package.json 文件中,來在 mocha 中使用 istanbul

istanbul cover _mocha $(find ./lib -name \"*.spec.js\" -not -path \"./node_modules/*\")

以後,你將獲得像這樣的代碼覆蓋率報告:

Node.js Unit Testing Code Coverage

你能夠點擊一下,看看帶註解的源代碼 - 哪些部分被測試,哪些部分沒有。

下一步

測試能夠省下不少麻煩 - 不過,依然不可避免要時常調試。下一章將學習如何調試 Node.js 應用程序

圖片描述

相關文章
相關標籤/搜索