资讯专栏INFORMATION COLUMN

【Node Hero】9. Node.js 单元测试

104828720 / 1689人阅读

摘要:基本上,测试金字塔描述你应该编写单元测试集成测试和端到端测试。集成测试要比端到端测试多,单元测试甚至要更多一些。应用程序单元测试编写单元测试,是为了看看给定的模块单元是否工作。

本文转载自:众成翻译
译者:网络埋伏纪事
链接:http://www.zcfy.cc/article/1754
原文:https://blog.risingstack.com/node-hero-node-js-unit-testing-tutorial/

本教程将会学习 Node.js 中的单元测试是什么,以及如何正确地测试你的应用程序。

测试 Node.js 应用程序

你可以把测试当作你创建的应用程序的保障措施。他们将不仅运行在你的本机上,还会在 CI 服务上,这样失败的构建就不会推送到产品系统中。

你也许会问:我的应用程序中该测试什么?我应该有多少测试?

答案因情而异,但是根据经验,你可以遵循测试金字塔制定的准则。

基本上,测试金字塔描述你应该编写单元测试集成测试端到端测试。集成测试要比端到端测试多,单元测试甚至要更多一些。

下面我们来看看如何为应用程序添加单元测试!

请注意,这里我们不打算讨论集成测试和端到端测试,因为它们远远超出了本教程的范畴。

*

Node.js 应用程序单元测试

编写单元测试,是为了看看给定的模块(单元)是否工作。所有依赖都被剔除了,意味着我们要为模块提供伪依赖。

应该为指定模块暴露的方法,而不是内部操作提供测试。

单元测试剖析

每个单元测试有如下结构:

测试设置

调用被测试的方法

断言

每个单元测试应该只测试一个关注点(当然,这不意味着你可以只添加一个断言)

用于 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 = "

title

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/85167.html

相关文章

  • Node Hero】8. 使用 Passport.js 进行 Node.js 身份验证

    摘要:本文转载自众成翻译译者网络埋伏纪事链接原文本教程中将学习如何使用和实现一个本地身份验证策略。我们将有一个用户页,一个备注页,和一些与身份验证相关的功能。下一步下一章主要涉及应用程序的单元测试。你会学习单元测试测试金字塔测试替代等概念。 本文转载自:众成翻译译者:网络埋伏纪事链接:http://www.zcfy.cc/article/1755原文:https://blog.risings...

    CoderStudy 评论0 收藏0
  • Node Hero】1. 开始使用 Node.js

    摘要:使用一个事件驱动的非阻塞式的模型,让它轻量而高效。也就是说提供了用编写服务器的可能性,这种服务器具有令人难以置信的性能。正如官方声明所说是一个使用与浏览器相同引擎的运行时。这意味着有两个发布版本稳定版和试验版。 本文转载自:众成翻译译者:网络埋伏纪事链接:http://www.zcfy.cc/article/1748原文:https://blog.risingstack.com/nod...

    hqman 评论0 收藏0
  • Node Hero】7. Node.js 项目结构

    摘要:本教程会学习如何正确组织一个项目的结构,从而在应用程序开始增长时避免混乱。项目结构的五个基本规则组织项目有不少可能的方式并且每种已知的方式都有其兴衰。过去在,我们有机会创建各种规模的高效应用程序,也获得了大量关于项目结构注意事项的见解。 本文转载自:众成翻译译者:网络埋伏纪事链接:http://www.zcfy.cc/article/1756原文:https://blog.rising...

    张红新 评论0 收藏0
  • Node Hero】2. 使用 NPM

    摘要:网站和使用同样的注册库来显示模块以及查找模块。使用在上一章开始使用中,当创建文件时,已经遇到了。此外,全局命名空间只包含公共模块。通过引入作用域包来解决此问题。下一步异步编程下一章学习使用回调和实现异步编程的原理。 本文转载自:众成翻译译者:网络埋伏纪事链接:http://www.zcfy.cc/article/1749原文:https://blog.risingstack.com/n...

    CarterLi 评论0 收藏0
  • Node Hero】5. Node.js 数据库教程

    摘要:是一种设计用于关系型数据库的查询语言。另一方面,数据库在最近十年变得相当流行。大多数数据库都有驱动程序可以用,它们在上也有库。我们已经完成了在中使用数据库所必须知道的所有基础知识。 本文转载自:众成翻译译者:网络埋伏纪事链接:http://www.zcfy.cc/article/1751原文:https://blog.risingstack.com/node-js-database-t...

    Hujiawei 评论0 收藏0

发表评论

0条评论

104828720

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<