Node.js TDD开发的起手式
个人觉得国内的开发氛围,对于代码进行单元测试是一件很奢侈的事。因为大部分人都是从大学才开始接触编程的,社会中浮躁的氛围也或多或少的带入到了大学校园,所以老师对同学们的代码也就仅仅要求上机执行成功就ok了,从而下意识的给同学们带来了一种你的代码只要跑通了,就完事大吉的错误想法,回过头来,仔细想想,我们可能只考虑到的代码执行的一种走向,而N-1种情况却丝毫没有考虑到。这为以后工作中开发成熟的用户产品卖下了隐患,当然我也不例外,也是受到大学氛围的荼毒,但是幸运的是,我遇到了一个开发要求相对较高的工作环境(__),我们小组以TDD形式开发后端,变量命名,见名知意,eslint代码格式检查,不予许写注释(WTF, 还有不许写注释的咯!其实,这个要求还挺好的,让我们更加关注代码的可读性),测试代码的覆盖率等等。
废话港了这么多,回到主题,当你看完我的BB后,你可以做到如下几个点:
- 搭建Node.js的测试环境
- http请求的的测试方式
- 测试异步方法
- mock依赖外部的代码
-
从所未有的对自己代码的自信
(key point)
Node.js 测试环境搭建
首先,package.json的配置
"dependencies": {
"babel-core": "^6.7.4",
"express": "^4.13.4",
"body-parser": "^1.15.0"
},
"devDependencies": {
"babel-cli": "^6.11.4",
"babel-polyfill": "^6.13.0",
"babel-preset-es2015": "^6.6.0",
"babel-plugin-transform-runtime": "^6.6.0",
"chai": "^3.3.0",
"express": "^4.13.4",
"isparta": "^4.0.0",
"istanbul": "^1.0.0-alpha",
"mocha": "^2.3.3",
"ramda": "^0.20.1",
"sinon": "^1.17.5",
"supertest": "^2.0.0"
}
为了要支持es6的语法,所以需要babel来进行代码的转化 babel官网 ,express是一个web框架,mocha则是大部分人的首选测试框架,chai是断言库(让代码更人性化),istanbul是一个和macha配合的非常好的输出测试覆盖率的库,sinon是stub或是mock代码的当下最为流行框架,supertest是方面测http请求的框架,body-parser是让你在http请求时更加方便的获取的get或是post带的参数。会玩的同学都懂吧,在项目路径中npm install下,这些库就到你的项目中了。
开发之前让我再啰嗦一下,想让babel正常工作,仅仅加入库还是不行的,看我截图,创建如下文件名的文件,以及文件内容(就是个配置文件而已)有兴趣可以看看babel网站的具体说明
Paste_Image.pngNode.js的入口文件
Paste_Image.png接下来是配置babel的最后一步了,重要可以使用es6的语法了,oh yeah
index.js 中注册babel,意思是app.js文件中的es6的语法都需要转化
require('babel-core/register')
require('./app')
require('babel-polyfill')
到现在你就可以写es6的语法了
http请求的的测试方式
我们以一个最简单的http请求来说明, 这是一个最简单的web server ,其中/hi 的get请求会返回welcome to hollywood!
import express from 'express'
const app = express()
app.get('/hi', (req, res) => {
res.send('welcome to hollywood!')
})
const server = app.listen(8000,() => {
const port = server.address().port
console.log(`hollywood server start on http://127.0.0.1:${port}`)
})
export default server
那我们就需要测试这个接口是不是真的返回的这串字符串。但是在写测试代码之前,还是需要配置下环境
Paste_Image.png
mocha-babel.js 的目的是测试代码也能使用es6语法,从截图我们也可以看出一般来说测试代码的结构和实际代码应该大致保持一致。
test/app.js
import supertest from 'supertest'
import app from '../src/app'
import chai from 'chai'
chai.should()
var request = supertest(app)
describe('GET /hi', () => {
it('should return text of welcome to hollywood!', (done) => {
request.get('/hi')
.expect((res) => {
const result = res.text
result.should.equals('welcome to hollywood!')
})
.end(done)
})
})
Paste_Image.png单元测试代码都是使用describe(desc: String, () => {})包裹的,第一个参数是对测试代码的描述,闭包是你具体要干的事情。
var request = supertest(app)
将我们要测试的server对象 包装成supertest,可以进行get 或是post请求,并且通过expect((res) => {})
中的res来进行判断是否是我们需要的数据。在进行判断是使用的should.equals()
,则是chai库所提供的断言。
如果你执行npm test
命令,看到如下结果,那么恭喜你,你已经变写成功了第一个测试代码, HOHO
测试异步方法
一般来说我们会吧异步的方法使用promise包装一下,结合es7中async/await 来使得代码看起来像同步一样, 可以假设一下,这个fs的writeFile方法是我们引用的第三方库所提供的回调方法。
src/common.js
import fs from 'fs'
export default class Common {
static async writeFile(studentName) {
const promise = new Promise((reslove, reject) => {
const callback = (err) => {
if (err) {
reject(err)
return
}
reslove()
}
fs.writeFile('db.txt', studentName, callback)
})
return promise
}
}
需要知道的是,一般第三方库的代码它自己已经测试了,我们在测试使用到这个第三方哭的单元测试时,都会选择把这个库给mock掉,放在以后第三方库代码升级导致我们测试不能通过的尴尬。
test/common.js
import Common from '../src/common'
import sinon from 'sinon'
import chai from 'chai'
import fs from 'fs'
chai.should()
let sandbox
const fsStub = () => {
sandbox.stub(fs, 'writeFile', (filePath, text, callback) => {
console.log('this is fake fs.writeFile()')
callback()
})
}
describe('Common', () => {
beforeEach(() => {
sandbox = sinon.sandbox.create()
fsStub()
})
afterEach(() => {
sandbox.restore()
})
describe('#writeFile(str)', () => {
it('should write text to some file', async () => {
// Given
const text = 'some text'
// When
await Common.writeFile(text)
})
})
})
Paste_Image.pngsinon 这个库就是mock对象方法而存在的,极大的提高了我们开发测试代码的效率
最后,需要导出专业的报告出我们测试代码的覆盖率,需要在package.json配置一下。
Paste_Image.png Paste_Image.png Paste_Image.png请自动忽略掉上图的覆盖率问题,仅仅是为了告诉如何配置而已。
起始测试还有很多的问题,需要你自己亲手动手敲才会遇到相应的问题,但是聪明的你肯定会迎刃而解。反正我是一个感性的人,看到自己写的代码全是绿色的钩钩是非常兴奋的,也许你尝试并坚持下来这个开发方式,你肯能也会寻到你代码世界的桃源!!!