在JavaScript最流行的测试框架中,Mocha(摩卡)一直处于前列,即使背靠Facebook的Jest,也很难大范围对Mocha取而代之,而Mocha之所以流行,很大一部分原因则是由于其和其他框架与工具良好的结合性,而与Mocha配合最好的框架,莫过于Chai(茶),Istanbul (nyc)(伊斯坦布尔), Sinon和Karma。正是这一批框架的良好结合,才让JavaScript的BDD与TDD无往不利。
摩卡,茶,伊斯坦布尔, Sinon 及其他 - 图1

1. Mocha

Mocha的官方介绍是简单、灵活而有趣(Simple,

flexible,fun)。是一套可以运行在Nodejs或者浏览器中的JavaScript测试框架。可以提供包括断言、异步、覆盖率报告等各项功能,当然,其中一些功能需要与其他框架配合才能更好地发挥作用。
与其他Nodejs包一样,Mocha可以简单地通过npm(当然也可以通过yarn或者淘宝的cnpm)全局安装或者仅安装在本项目中。以cnpm为例。

  1. #全局安装
  2. cnpm install --global mocha
  3. #安装在项目开发依赖中
  4. cnpm install --save-dev mocha

Mocha默认自动运行test文件夹下的测试文件,因此需要在项目文件夹下新建test文件夹以供测试,如果要将测试命令添加到项目中,需要在项目package.json文件添加:

  1. "scripts": {
  2. "test": "mocha"
  3. }

Mocha默认支持Nodejs内置的assert模块,不过,如果要使测试更灵活,则需要安装断言库——Chai(茶)便是这些断言库中最流行的。
一段典型的使用内置assert模块的测试代码如下(官方代码):

  1. var assert = require('assert');
  2. describe('Array', function() {
  3. describe('#indexOf()', function() {
  4. it('should return -1 when the value is not present', function() {
  5. assert.equal([1, 2, 3].indexOf(4), -1);
  6. });
  7. });
  8. });

摩卡,茶,伊斯坦布尔, Sinon 及其他 - 图2

2. Chai

Chai(茶)可以说是Mocha的黄金搭档,是一个支持BDD(行为驱动开发)和TDD(测试驱动开发)的断言库,同样可以运行在Node和浏览器环境中。Chai的安装和Mocha类似,而一般来说两者往往也是同时安装的。
Chai支持Should、Expect和Assert三种风格的断言,可以按照个人习惯和喜好选择使用。一段使用了Chai的典型金丝雀(canary)测试代码如下。在实际使用中,金丝雀测试通常是为了确认安装环境没有问题的第一个测试。

  1. var expect=require('chai').expect;
  2. describe('Canary tests',()=>{
  3. it('should pass the canary test',()=>{
  4. expect(true).to.be.true;
  5. });
  6. });

为了保持测试语言风格的一致性,Chai断言库也有一些扩展库,比如,为了实现在断言测试中保持chai的风格,可以安装chai-as-promised库,为了在karma中使用chai风格断言,可以安装karma-chai-as-promised, 而为了在sinon中使用chai风格断言,则可能需要安装sinon-chai.以cnpm下安装为例。

  1. cnpm install --save-dev chai
  2. cnpm install --save-dev chai-as-promised
  3. cnpm install --save-dev karma-chai-as-promised
  4. cnpm install --save-dev sinon-chai

2.1 Chai-as-promised

在没有安装chai-as-promised的情况下,一段用于测试promise的代码可能是这样(官方代码)。

doSomethingAsync().then(
    function (result) {
        result.should.equal("foo");
        done();
    },
    function (err) {
       done(err);
    }
);

而安装了chai-as-promised库之后则可能是如下的样子(expect风格)。

var chai=require('chai');
var chaiAsPromised=require('chai-as-promised');
var expect=chai.use(chaiAsPromised).expect;
return expect(doSomethingAsync())to.eventually.eql('foo');

chai-as-promised断言库部分接口扩展如下(expect风格):

return expect(PromiseFn).to.be.fulfilled;
return expect(PromiseFn).to.be.eventually.eql('foo');
return expect(PromiseFn).to.be.rejected;
return expect(PromiseFn).to.be.rejectedWith(Error);
return expect(PromseFn.resolve({'foo':'bar'})).to.eventually.have.property('foo');
expect(PromiseFn).to.be.fulfilled.notify(done);

2.2 Sinon-Chai

一段Sinon-chai官方的测试代码示例如下:

"use strict";
var chai = require("chai");
var sinon = require("sinon");
var sinonChai = require("sinon-chai");
var expect = chai.expect;
chai.use(sinonChai);

function hello(name, cb) {
    cb("hello " + name);
}

describe("hello", function () {
    it("should call callback with correct greeting", function () {
        var cb = sinon.spy();

        hello("foo", cb);

        expect(cb).to.have.been.calledWith("hello foo");
    });
});

摩卡,茶,伊斯坦布尔, Sinon 及其他 - 图3

3. istanbul(nyc)

istanbul(伊斯坦布尔)是nyc包的曾用名,nyc是一个测试覆盖率工具,可以方便检查代码的测试情况与覆盖率。同样使用npm包安装。

cnpm install --save-dev nyc

安装后,在package包中配置脚本即可直接运行。如果不想覆盖使用mocha的test命令,可以增加一个诸如cov或者coverage的命令,可以在命令行启动

{
  "scripts": {
    "cov": "nyc mocha"
  }
}

nyc也支持配置文件及通过命令扩展输出不同风格的报告。

{
  "scripts": {
    "cov": "nyc --reporter=html --reporter=text mocha"
  }
}
npm run cov

摩卡,茶,伊斯坦布尔, Sinon 及其他 - 图4

4.Sinon

sinon的名称可能是从法语不然/否则来的,不过这只是猜测,并没有官方的说法。sinon并不是测试框架,却是宣称可以和任何JavaScript测试框架配合的测试库,Sinon的主要作用是提供spies、stubs和mocks功能,用于在测试工程中为被测试代码提供环境。
在BDD或者TDD风格的程序开发中,在测试一段程序时,往往还不存在与其相配合的代码片段(还没有写出来),也没有或者不能使用真实的网络、数据库等进行测试,因此就需要sinon库来进行模拟与仿真,保证待测代码片段运行的上下文,以分段进行代码测试。
在Sinon的几个库中,spies顾名思义,与间谍类似,主要用于监听程序/函数调用过程的信息,通过这些信息验证代码是否正常运行。spies本身并不会影响函数上下文运行环境,因此需要其他几个库的配合;stub的作用是取代某个函数与模块,用于代替原来的函数实现某部分功能,比如一段解析文件内容的函数A,需要另一端打开并读取文件的函数B来提供文件的原始内容,那么在测试A的时候,就可以通过stub函数虚拟出来一个函数B来提供测试内容,这时真实的函数B并不会运行(也可能还没有真正写出来实现),从而做到各个模块(包括模块的特定分支)独立运行和测试;mock模块与stub有一些类似,但又不完全相同,mock模块可以验证某些函数或者模块更具体的功能和行为,比如函数A在异常时会调用函数B,那么通过Mock函数B可以验证B被调用的次数,以及B被调用的次数、包含的参数等等。
为了保证测试的独立性,在sinon中提供了沙盒sandbox模块,通过sandbox模块可以避免测试对项目代码带来污染的可能。而除了上述几个模块外,sinon还提供了faketimer,fake等等实用而强大的模块,模拟真是环境中的setTimeout,setIntervals等延时条件,加快测试进度。
一段官方的sinon测试代码如下,在安装了如前所述的sinon-chai模块后,也可以使用expect或者should风格的测试语句。

const expect=require('chai').expect;
const sinon=require('sinon');
const fs=require('fs');
var Stockfetch=require('../src/stockfetch');
describe('Stockfetch tests',()=>{

    let stockfetch;
    let sandbox;
    beforeEach(()=>{
        stockfetch=new Stockfetch();
        sandbox=sinon.createSandbox();
    })
    afterEach(()=>{
        sandbox.restore();
    })
    it('should pass this canary test',()=>{
        expect(true).to.be.true;
    });

摩卡,茶,伊斯坦布尔, Sinon 及其他 - 图5

5. Karma

Karma主要用于验证客户端代码的行为,它是一个轻量级的可以用于不同浏览器环境的服务器,安装karma时,可能需要根据情况选择安装和配置不同的插件,比如:

cnpm install --save-dev karma
cnpm install --save-dev karma-mocha
cnpm install --save-dev karma-chai
cnpm install --save-dev karma-chrome-launcher
cnpm install --save-dev karma-firefox-launcher
cnpm install --save-dev karma-clear-screen-reporter
cnpm install --save-dev karma-cli
cnpm install --save-dev karma-coverage

在安装完之后,要对karma进行初始化及配置,运行。

node node_modules/karma/bin/karma init

根据实际情况依次选择测试框架(默认使用jasmine,如果使用了mocha需要修改),浏览器等参数,在初始化完成后,也可以在生成的karma.conf.js文件中修改相应部分内容以修改karma的配置。karma支持自动化监控运行,即只要源代码有所修改,就会自动运行测试程序。
摩卡,茶,伊斯坦布尔, Sinon 及其他 - 图6