【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 应用程序

图片描述

相关文章
相关标签/搜索