mocha、chai、sinon和istanbul实现100%单元测试覆盖率

敏捷软件开发中,最重要实践的就是测试驱动开发,在单元测试层面,我们试着实现一个重要的指标就是测试覆盖率。测试覆盖率衡量我们的代码是否已经全部被测试到了。

但是指标本身不是目的,借助测试覆盖率检查,我们希望发现那些未被测试覆盖的代码,从而去思考如何测试那些代码的逻辑,进而更好的设计重构代码,让代码有更高的质量[1]

谈到测试,正好最近在看《数学之美》,书中谈到的关于信息的一段话。我们要把代码的行为从不确定性,变成确定性,也是一样。从黑盒变成白盒,没有什么神奇的力量,唯有提供足够的信息,而测试中的断言就是信息,测试覆盖率也是信息,测试覆盖率可以认为是一种间接的信息,可以消除一部分不确定性,而充分的断言,则提供了更直接的信息。加上测试覆盖率检查,就能够提供足够的信息,来断言代码的行为是否符合期望。

测试的相关技术

IstanbulJavaScript程序的代码覆盖率工具,以土耳其最大城市伊斯坦布尔命名。Istanbul会对代码进行转换,生成语法树,然后在相应位置注入统计代码,执行之后根据注入的全局变量的值,统计代码执行的次数;在对代码的转换完成之后,Istanbul会调用test runner,例如mocha,执行转换之后的代码的测试,生成测试报告。

Mocha是一种测试框架,也就是运行测试的工具,类似Jasmine、Karma和Ava。跟JUnit的注解一样,mocha作为执行器,用descibeit方法,来定义test suit,为不同的测试分组。mocha本身并不提供assert断言,所以要提供更加有表现力的断言,可以搭配chai使用,当然也可以使用nodejs提供的assert模块

在我们的代码中,总会有一些复杂的逻辑或者依赖io、网络的异步代码,用直接的方法难以测试,这时可以通过sinon简化复杂代码的测试。Sinon通过创建Test Double也就是测试替身,将我们代码中依赖的一些函数或者类,替换成测试替身,而我们可以对测试替身的行为进行设置,模拟我们的代码需要的结果,从而让难以测试的代码逻辑被执行。

为nodejs项目配置测试环境

1 安装相应的依赖包

mocha和istanbul可以全局安装,也可以只在当前项目安装。

npm install --save-dev mocha chai sinon istanbul

安装完成之后,在package.json文件的scripts下,添加执行测试和测试覆盖率检查的命令

{
  ...
  "scripts":{
    "coverage": "istanbul cover _mocha -- -R spec --timeout 5000 --recursive",
    "coverage:check": "istanbul check-coverage",
  }
  ...
}

运行npm run coveragenpm run coverage:check,就可以生成测试报告,前者生成测试报告,后者则是检查测试覆盖率是否达到要求。

2 配置Istanbul

istanbul相关的执行参数,可以在命令行下执行时传递参数来制定,也可以在yaml格式的.istanbul.yml文件中配置。简单贴出一些重要的配置项

instrumentation:
  root: .   # 执行的根目录
  extensions:
    - .js   # 检查覆盖率的文件扩张名
  excludes: ['**/benchmark/**']

  ... ...

reporting:
  print: summary
  reports: [lcov, text, html, text-summary] # 生成报告的格式
  dir: ./coverage   # 生成报告保存的目录
  watermarks:       # 在不同覆盖率下会显示使用不同颜色
    statements: [80, 95]
    ... ...
check:
  global:
    statements: 100
    branches: 100
    lines: 100
    functions: 100

最后的check是项目要通过覆盖率检查需要达到的测试覆盖率,测试覆盖率包括四个维度,分别是语句覆盖率、分支覆盖率、行覆盖率和函数覆盖率。如果达不到设定的指标,在执行的时候会报错,项目的测试就无法通过自动化的持续集成。

编写测试代码

敏捷软件开发中的测试驱动开发,意在通过先写测试,根据调用者的契约,设计如何实现代码,从而写出更加容易测试的代码,提高代码的质量。也是我们练习测试的应该考虑的方向。

1 一段简单的mocha测试代码

利用chai提供的expect断言,我们可以用BDD的方式,写出更加符合代码预期行为的测试用例。

var chai = require('chai')

chai.should()
var expect = chai.expect
var assert = chai.assert

describe('basic test', function () {
  describe('simple', function () {
    it('data check', function () {
      var data = { name: "test" }

      assert.isNotNull(data, 'data should not be null')
      expect(data).to.be.an('object')
      expect(data).to.have.all.keys(['name'])
      expect(data).to.deep.include({name: 'test'});
    });
  });
});

2 用Sinon模拟文件读写

... 同上 ...
var sinon = require('sinon')
var fs = require('fs')

describe('sinon', function () {
  it("should mock readFile", function(done){
    sinon.stub(fs, 'readFile').callsFake(function (path, callback) { callback(new Error('read error')) })

    fs.readFile("any file path", function(err,data){
      assert.isNotNull(err)
      done()
    })
    assert(fs.readFile.calledOnce)
  });
});

在mocha测试框架中,如果我们调用的是异步的代码,那么需要显示的调用it回调函数的done方法,告诉mocha异步调用什么时候结束。否则的话,测试会挂起,直到设置的超时时间结束。

Sinon将测试替身分为spy、stub和mock,其中:

  • Spy, 可以提供函数调用的信息,但不会改变函数的行为
  • Stub, 提供函数的调用信息,并且可以像示例代码中一样,让被stubbed的函数返回任何我们需要的行为。
  • Mock, 通过组合spies和stubs,使替换一个完整对象更容易。

本文的讨论篇幅有限,暂时不详细介绍各种sinon的使用方法,以后再通过其他文章专门介绍。

持续集成

完成所有代码之后,我们可以将代码发布到github,然后使用持续集成工具travis检查代码,将生成的测试报告上传到coverall上,这样就可以在项目中显示项目状态和测试覆盖率的badges。

具体使用方法,可以参看官方网站,使用coveralls需要在项目中安装依赖包npm i -D coveralls。并且添加package.json执行脚本istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js

通常的nodejs项目.travis.yml配置如下:

language: node_js
node_js:
  - "7.6.0"
install:
  - npm install
script:
  - npm test
after_script:
  - npm run coverall

本文涉及到的相关技术可以参看最近的一个项目,该项目完整的使用了文中涉及到的技术。

https://github.com/liuwill/find-ip-location

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

推荐阅读更多精彩内容

  • 本次翻译时间为2016年9月底,目前Mocha的版本为3.1.0。官方文档地址: http://mochajs.o...
    Awey阅读 17,518评论 4 34
  • 在团队合作中,你写好了一个函数,供队友使用,跑去跟你的队友说,你传个A值进去,他就会返回B结果了。过了一会,你队友...
    JC_Huang阅读 5,278评论 1 29
  • 前言 本篇文章是我在学习前端自动化单元测试时的一些思路整理,之前也从未接触过单元测试相关工具,如有错漏,请读者斧正...
    Awey阅读 12,617评论 8 38
  • 单元测试不是一个小工程,需要多用些时间才能做好,不要希望通过这个文章就能掌握单元测试,这只是一个入门,需要自己动手...
    勇不言弃92阅读 7,781评论 9 60
  • 虽然,是一篇命题作文,但是我,真的,发自肺腑的,感恩:加入百姓网,遇见你们! 我是一枚财务妹子,呵,你脑海中大概有...
    olivia阅读 323评论 0 2