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.png

Node.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)
    })
})

单元测试代码都是使用describe(desc: String, () => {})包裹的,第一个参数是对测试代码的描述,闭包是你具体要干的事情。var request = supertest(app) 将我们要测试的server对象 包装成supertest,可以进行get 或是post请求,并且通过expect((res) => {})中的res来进行判断是否是我们需要的数据。在进行判断是使用的should.equals(),则是chai库所提供的断言。
如果你执行npm test命令,看到如下结果,那么恭喜你,你已经变写成功了第一个测试代码, HOHO

Paste_Image.png

测试异步方法

一般来说我们会吧异步的方法使用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)
        })
    })
})

sinon 这个库就是mock对象方法而存在的,极大的提高了我们开发测试代码的效率

Paste_Image.png

最后,需要导出专业的报告出我们测试代码的覆盖率,需要在package.json配置一下。

Paste_Image.png
Paste_Image.png
Paste_Image.png

请自动忽略掉上图的覆盖率问题,仅仅是为了告诉如何配置而已。
起始测试还有很多的问题,需要你自己亲手动手敲才会遇到相应的问题,但是聪明的你肯定会迎刃而解。反正我是一个感性的人,看到自己写的代码全是绿色的钩钩是非常兴奋的,也许你尝试并坚持下来这个开发方式,你肯能也会寻到你代码世界的桃源!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,401评论 25 707
  • 写在开头 先说说为什么要写这篇文章, 最初的原因是组里的小朋友们看了webpack文档后, 表情都是这样的: (摘...
    Lefter阅读 5,269评论 4 31
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,587评论 18 139
  • 这两天写作群里关于交作业的时间和违规规定出现了一些异议。有两位写友因为工作生活的忙碌,认为当天夜里12点之前交稿的...
    惠茹姐姐阅读 263评论 3 1
  • 又到了新年许愿的时候啦!2017年你的愿望是工作顺利、家庭幸福、还是爱情甜蜜?不过相信有一个愿望每个人都会有,那就...
    听秋月说阅读 252评论 0 0