本文转载自:众成翻译
译者:网络埋伏纪事
链接: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 单元测试的模块
对于单元测试,我们打算用如下模块:
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 应用程序。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。