代码改变世界

学习测试框架Mocha

2018-02-05 21:43  龙恩0707  阅读(4009)  评论(0编辑  收藏  举报

学习测试框架Mocha

注意:是参考阮老师的文章来学的。虽然阮老师有讲解,但是觉得自己敲一遍,然后记录一遍效果会更好点。俗话说,好记性不如烂笔头。

   Mocha 是javascript测试框架之一,可以在浏览器和Node环境下使用,除了Mocha测试框架之外,类似的测试框架还有Jasmine, Karma, Tape等。
可以使用npm全局安装:如下命令:

npm install -g mocha

也可以作为项目的依赖进行安装,如下命令:

npm install --save-dev mocha

如下所有的测试代码在github上,请查看github上的代码

Mocha的作用是运行测试脚本,我们先来编写一个js代码吧,下面是一个简单的加法模块 add.js代码:

function add(x, y) {
  return x + y;
}
module.exports = add;

要测试上面的代码是否对的,因此就要编写测试脚本,测试脚本与所要测试的源码脚本同名,但是后缀名为 .test.js或 .spec.js, 如:xx.test.js 或 xx.spec.js,比如上面的add.js的测试脚本可以叫 add.test.js 或 add.spec.js,因此我们可以在add.js的同目录下新建 add.test.js,(可以查看demo1文件代码)
编写代码如下:

var add = require('./add.js');
var expect = require('chai').expect;

describe('加法函数的测试', function() {
  it('1加1应该等于2', function() {
    expect(add(1, 1)).to.be.equal(2);
  });
});

如上代码就是一个测试脚本代码,测试脚本可以包含一个或多个describe块,describe块称为 "测试套件",表示一组相关的测试,它是一个函数,有两个参数,第一个参数是测试套件的名称,第二个参数是一个实际执行的函数。

每个describe块也可以包含一个或多个it块,it块称为 "测试用例",表示一个单独的测试,是测试的最小单位,它也是一个函数,第一个参数也是测试用例的名称,第二个参数是一个实际执行的函数。

二. 理解断言库
断言库可以理解为比较函数,也就是断言函数是否和预期一致,如果一致则表示测试通过,如果不一致表示测试失败。mocha本身是不包括断言库的,所以必须引入第三方断言库的,目前比较受欢迎的断言库有 should.js, expect.js, chai.
should.js BDD风格
expect.js expect风格的断言
chai expect(), assert() 和 should的断言
Mocha默认使用的是BDD的风格。expect和should都是BDD的风格,二者使用相同的链式语言来组织断言的,但不同在于他们初始化断言的方式,expect使用
构造函数来创建断言对象实例,而should通过为 Object.prototype新增方法来实现断言(should不支持IE),expect直接指向 chai.expect,
should则是 chai.should();

上面的代码中 expect 是断言的意思,该作用是判断源码的实际执行结果与预期结果是否一致,如果不一致就抛出一个错误,因此在执行上面代码之前,
我们需要在项目中安装 chai, 如下命令:

npm install --save-dev chai

所有的测试用例(it块)都应该含有一句或多句断言,是编写测试用例的关键,Mocha本身不包含断言,断言是由断言库来实现的,因此需要先引入断言库。
如下代码:

var expect = require('chai').expect;

上面代码是引用 chai 断言库,使用的是 expect断言风格。

expect 官网API(http://chaijs.com/api/bdd/).

如下是一些常用的比较;

// equal 相等或不相等
expect(4 + 5).to.be.equal(9);
expect(4 + 5).to.be.not.equal(10);
expect('hello').to.equal('hello');  
expect(42).to.equal(42);  
expect(1).to.not.equal(true);  
expect({ foo: 'bar' }).to.not.equal({ foo: 'bar' });  
expect({ foo: 'bar' }).to.deep.equal({ foo: 'bar' });

// above 断言目标的值大于某个value,如果前面有length的链式标记,则可以用来判断数组长度或者字符串长度
expect(10).to.be.above(5);
expect('foo').to.have.length.above(2);  
expect([ 1, 2, 3 ]).to.have.length.above(2); 
类似的还有least(value)表示大于等于;below(value)表示小于;most(value)表示小于等于

// 判断目标是否为布尔值true(隐式转换)
expect('everthing').to.be.ok;
expect(1).to.be.ok;  
expect(false).to.not.be.ok;
expect(undefined).to.not.be.ok;  
expect(null).to.not.be.ok; 

// true/false 断言目标是否为true或false
expect(true).to.be.true;  
expect(1).to.not.be.true;
expect(false).to.be.false;  
expect(0).to.not.be.false;

// null/undefined 断言目标是否为null/undefined
expect(null).to.be.null;  
expect(undefined).not.to.be.null;
expect(undefined).to.be.undefined;  
expect(null).to.not.be.undefined;


// NaN  断言目标值不是数值
expect('foo').to.be.NaN;
expect(4).not.to.be.NaN;

// 判断类型大法(可以实现上面的一些例子):a/an
expect('test').to.be.a('string');
expect({ foo: 'bar' }).to.be.an('object');
expect(foo).to.be.an.instanceof(Foo);
expect(null).to.be.a('null');  
expect(undefined).to.be.an('undefined');
expect(new Error).to.be.an('error');
expect(new Promise).to.be.a('promise');

// 包含关系:用来断言字符串包含和数组包含。如果用在链式调用中,可以用来测试对象是否包含某key 可以混着用。
expect([1,2,3]).to.include(2);
expect('foobar').to.contain('foo');
expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo');

// 判断空值
expect([]).to.be.empty;
expect('').to.be.empty;
expect({}).to.be.empty;

// match
expect('foobar').to.match(/^foo/);
    
// exist 断言目标既不是null也不是undefined
var foo = 'hi' , bar = null, baz;
expect(foo).to.exist;  
expect(bar).to.not.exist;  
expect(baz).to.not.exist;

// within断言目标值在某个区间范围内,可以与length连用
expect(7).to.be.within(5,10);  
expect('foo').to.have.length.within(2,4);  
expect([ 1, 2, 3 ]).to.have.length.within(2,4);

// instanceOf 断言目标是某个构造器产生的事例
var Tea = function (name) { this.name = name; } , Chai = new Tea('chai');
expect(Chai).to.be.an.instanceof(Tea);  
expect([ 1, 2, 3 ]).to.be.instanceof(Array); 

// property(name, [value])  断言目标有以name为key的属性,并且可以指定value断言属性值是严格相等的,此[value]参数为可选,如果使用deep链式调用,可以在name中指定对象或数组的引用表示方法
// simple referencing
var obj = { foo: 'bar' };  
expect(obj).to.have.property('foo');  
expect(obj).to.have.property('foo', 'bar');// 类似于expect(obj).to.contains.keys('foo')

// deep referencing
var deepObj = {  
  green: { tea: 'matcha' },
  teas: [ 'chai', 'matcha', { tea: 'konacha' } ]
};
expect(deepObj).to.have.deep.property('green.tea', 'matcha');  
expect(deepObj).to.have.deep.property('teas[1]', 'matcha');  
expect(deepObj).to.have.deep.property('teas[2].tea', 'konacha'); 

// ownproperty 断言目标拥有自己的属性,非原型链继承
expect('test').to.have.ownProperty('length'); 

// throw 断言目标抛出特定的异常
var err = new ReferenceError('This is a bad function.');  
var fn = function () { throw err; }  
expect(fn).to.throw(ReferenceError);  
expect(fn).to.throw(Error);  
expect(fn).to.throw(/bad function/);  
expect(fn).to.not.throw('good function');  
expect(fn).to.throw(ReferenceError, /bad function/);  
expect(fn).to.throw(err);  
expect(fn).to.not.throw(new RangeError('Out of range.'));  

// satisfy(method) 断言目标通过一个真值测试
expect(1).to.satisfy(function(num) { return num > 0; })

三. mocha测试代码如何运行?
上面的add.test.js 编写完成后,我们需要运行测试代码了,进入add.test.js代码的目录后,执行如下命令可运行:

mocha add.test.js

如下结果:

$ mocha add.test.js


  加法函数的测试
    ✓ 1加1应该等于2


  1 passing (10ms)

如上所示,表示测试脚本通过了测试,共用一个测试用例,耗时10毫秒。

mocha命令后面也可以指定多个文件,如下命令:

mocha xx.test.js yy.test.js

3-1 把测试文件放入test目录下
mocha默认运行test子目录里面的测试脚本,我们一般情况下,可以把测试脚本放在test目录下,然后进入对应的目录,直接执行mocha命令即可:
请看demo2;
如下目录页面:

demo2
   |---- src
   |  |--- add.js
   |  |--- multiple.js
   |  |--- reduce.js
   |---- test
   |  |--- dir
   |  | |--- multiple.test.js 
   |  |
   |  |--- add.test.js

src/add.js 代码如下:

function add(x, y) {
  return x + y;
}
module.exports = add;

src/multiple.js代码如下:

function multiply(x, y) {
  return x * y;
}
module.exports = multiply;

src/reduce.js 代码如下:

function add(x, y) {
  return x - y;
}
module.exports = add;

test/add.test.js代码如下:

var add = require('../src/add.js');
var expect = require('chai').expect;

describe('加法函数的测试', function() {
  it('1 加 1 应该等于 2', function() {
    expect(add(1, 1)).to.be.equal(2);
  });

  it('任何数加0应该等于自身', function() {
    expect(add(1, 0)).to.be.equal(1);
  });
});

test/dir/multiple.test.js代码如下:

var multiply = require('../../src/multiply');
var expect = require('chai').expect;

describe('乘法函数的测试', function() {
  it('1 乘 1 应该等于 1', function() {
    expect(multiply(1, 1)).to.be.equal(1);
  });
})

当我在demo2项目目录下,运行 mocha 命令后,执行如下:

$ mocha


  加法函数的测试
    ✓ 1 加 1 应该等于 2
    ✓ 任何数加0应该等于自身


  2 passing (10ms)

我们可以看到,test子目录里面的测试脚本执行了,但是test目录下还有dir这样的目录里面的测试脚本文件并没有执行,所以我们可以得出一个结论是,mocha
命令只会执行test第一层目录下所有文件,并不能执行嵌套目录下的文件。
为了执行所有嵌套目录下的文件,我们可以 mocha命令后面加一个参数 --recursive 参数,如下命令:

$ mocha --recursive


  加法函数的测试
    ✓ 1 加 1 应该等于 2
    ✓ 任何数加0应该等于自身

  乘法函数的测试
    ✓ 1 乘 1 应该等于 1


  3 passing (11ms)

四. 理解使用通配符
命令行中测试脚本文件,可能会有多个脚本文件需要被测试,这时候我们可以使用通配符,来做批量操作。
比如我们在 demo2下新建spec目录,文件目录变成如下结构:

demo2
   |---- src
   |  |--- add.js
   |  |--- multiple.js
   |  |--- reduce.js
   |---- test
   |  |--- dir
   |  | |--- multiple.test.js 
   |  |
   |  |--- add.test.js
   |
   |----- spec
   |  |--- add.js
   |  |--- reduce.js

demo2/spec/add.js 代码如下:

var add = require('../src/add.js');
var expect = require('chai').expect;

describe('加法函数的测试', function() {
  it('1 加 1 应该等于 2', function() {
    expect(add(1, 1)).to.be.equal(2);
  });

  it('任何数加0应该等于自身', function() {
    expect(add(1, 0)).to.be.equal(1);
  });
});

demo2/spec/reduce.js代码如下:

var reduce = require('../src/reduce.js');
var expect = require('chai').expect;

describe('减法函数的测试', function() {
  it('2 减 1 应该等于 1', function() {
    expect(reduce(2, 1)).to.be.equal(1);
  });
});

我们可以运行如下命令,执行多个测试脚本文件:

mocha spec/{add,reduce}.js

命令效果如下:

$ mocha spec/{add,reduce}.js


  加法函数的测试
    ✓ 1 加 1 应该等于 2
    ✓ 任何数加0应该等于自身

  减法函数的测试
    ✓ 2 减 1 应该等于 1


  3 passing (11ms)

或者直接后面加*号,匹配所有的文件,和js中的正则类似:如下命令:

$ mocha spec/*.js


  加法函数的测试
    ✓ 1 加 1 应该等于 2
    ✓ 任何数加0应该等于自身

  减法函数的测试
    ✓ 2 减 1 应该等于 1


  3 passing (10ms)

五. 命令行参数常用的有哪些?
5.1 --help
--help参数,用来查看Mocha的所有命令行参数,如下命令所示:
mocha --help

5.2 --reporter
--reporter参数用来指定测试报告的格式,默认是spec格式。

$ mocha
# 等同于
$ mocha --reporter spec

我们可以使用 mocha --reporters 命令查看所有内置的报告格式。如下命令:

$ mocha --reporters    

    dot - dot matrix
    doc - html documentation
    spec - hierarchical spec list
    json - single json object
    progress - progress bar
    list - spec-style listing
    tap - test-anything-protocol
    landing - unicode landing strip
    xunit - xunit reporter
    min - minimal reporter (great with --watch)
    json-stream - newline delimited json events
    markdown - markdown documentation (github flavour)
    nyan - nyan cat!

我们可以使用 mochawesome(http://adamgruber.github.io/mochawesome/) 模块,可以生成漂亮的HTML格式的报告。
首先我们需要安装 mochawesome模块,如下命令行:

npm install --save-dev mochawesome
$ ../node_modules/.bin/mocha --reporter mochawesome


  加法函数的测试
    ✓ 1 加 1 应该等于 2
    ✓ 任何数加0应该等于自身


  2 passing (10ms)

[mochawesome] Report JSON saved to /Users/tugenhua/个人demo/vue1204/mocha/demo2/mochawesome-report/mochawesome.json

[mochawesome] Report HTML saved to /Users/tugenhua/个人demo/vue1204/mocha/demo2/mochawesome-report/mochawesome.html

因此会在demo2项目目录下生成 mochawesome-report 文件目录,我们可以查看 mochawesome/mochawesome.html文件打开看一下即可:

5.3 --watch
--watch 参数用来监听指定的测试脚本,只要测试脚本有变化,就会自动运行mocha。我们在demo2目录下,运行 mocha --watch命令,
然后修改脚本文件,可以看到如下:

$ mocha --watch

  加法函数的测试
    ✓ 1 加 1 应该等于 2
    ✓ 任何数加0应该等于自身


  2 passing (9ms)

  加法函数的测试
111 加 1 应该等于 2
11
    ✓ 任何数加0应该等于自身


  2 passing (2ms)

我在add.js 加了一句 console.log(11),上面可以看到也同样重新执行了 mocha命令。

5.4 --bail
--bail参数指定只要有一个测试用例没有通过,就停止执行后面的测试用例。
mocha --bail

5.5 --grep
--grep参数用于搜索测试用例的名称(即it块的第一个参数),然后只执行到匹配的测试用例。
如下代码命令:

$ mocha                

  加法函数的测试
111 加 1 应该等于 2
11
    ✓ 任何数加0应该等于自身


  2 passing (10ms)


~/个人demo/vue1204/mocha/demo2 on  Dev_20171115_wealth!
$ mocha --grep "1 加 1"

  加法函数的测试
111 加 1 应该等于 2


  1 passing (12ms)

5.6 --invert
--invert参数表示只运行不符合条件的测试脚本,必须与 --grep参数配合使用。
如下执行结果:

 

~/个人demo/vue1204/mocha/demo2 on  Dev_20171115_wealth! 
$ mocha                         

  加法函数的测试
111 加 1 应该等于 2
11
    ✓ 任何数加0应该等于自身


  2 passing (13ms)

~/个人demo/vue1204/mocha/demo2 on  Dev_20171115_wealth! 
$ mocha --grep "1 加 1" --invert

  加法函数的测试
11
    ✓ 任何数加0应该等于自身

  1 passing (10ms)

六. 配置文件 mocha.opts
Mocha的测试脚本文件 允许放在test目录下面,但是我们也可以在test目录下新建一个mocha.opts文件,把命令行写在该里面,还是看demo2目录结构,
在test目录下新建 mocha.opts文件,如下目录结构:

demo2
   |---- src
   |  |--- add.js
   |  |--- multiple.js
   |  |--- reduce.js
   |---- test
   |  |--- dir
   |  | |--- multiple.test.js 
   |  |
   |  |--- add.test.js
   |  |--- mocha.opts
   |
   |----- spec
   |  |--- add.js
   |  |--- reduce.js

mocha.opts文件写入如下命令:

--recursive
--reporter tap

然后执行mocha命令,就可以执行测试中的所有测试代码:

~/个人demo/vue1204/mocha/demo2 on  Dev_20171115_wealth! 
$ mocha
1..3
11
ok 1 加法函数的测试 1 加 1 应该等于 2
11
ok 2 加法函数的测试 任何数加0应该等于自身
ok 3 乘法函数的测试 1 乘 1 应该等于 1
# tests 3
# pass 3
# fail 0

当然如果测试用例不是存放在test子目录下,可以在mocha.opts写入如下内容:

server-tests
--recursive
--reporter tap 

上面代码指定允许 server-tests 目录及其子目录之中的测试脚本。

七: ES6的测试;
如果测试脚本是用ES6编写的,那么允许测试之前,需要先用babel转码,我们在test目录下新建 es6.test.js文件,先看目录结构如下:

demo2
   |---- src
   |  |--- add.js
   |  |--- multiple.js
   |  |--- reduce.js
   |---- test
   |  |--- dir
   |  | |--- multiple.test.js 
   |  |
   |  |--- add.test.js
   |  |--- es6.test.js
   |  |--- mocha.opts
   |
   |----- spec
   |  |--- add.js
   |  |--- reduce.js

es6.test.js 代码如下:

import add from '../src/add.js';
import chai from 'chai';
let expect = chai.expect;

describe('加法函数的测试', function() {
  it('1 加 1 应该等于 2', function() {
    expect(add(1, 1)).to.be.equal(2);
  });

  it('任何数加0应该等于自身', function() {
    expect(add(1, 0)).to.be.equal(1);
  });
});

如果我们直接在demo2命令行中允许 mocha命令就会报错,如下报错:

$ mocha
/Users/tugenhua/个人demo/vue1204/mocha/demo2/test/es6.test.js:1
(function (exports, require, module, __filename, __dirname) { import add from '../src/add.js';
                                                              ^^^^^^

SyntaxError: Unexpected token import

因此我们需要ES6转码,需要安装Babel。命令如下:

npm install babel-core babel-preset-es2015 --save-dev

然后我们需要在项目的目录下,新建一个 .babelrc配置文件。
添加如下代码如下:

{
  "presets": ['es2015']
}

最后,我们使用 --compilers 参数指定测试脚本的转码器。

../node_modules/mocha/bin/mocha --compilers js:babel-core/register

如上命令代码,--compilers 参数后面是一个使用冒号分割的字符串,冒号左边是文件的后缀名,右边是用来处理这一类文件的模块名,意思是说,先使用
babel-core/register模块,处理一下 .js文件;如下命令行代码所示:

~/个人demo/vue1204/mocha/demo2 on  Dev_20171115_wealth! 
$ ../node_modules/mocha/bin/mocha --compilers js:babel-core/register
1..5
(node:55513) DeprecationWarning: "--compilers" will be removed in a future version of Mocha; see https://git.io/vdcSr for more info
11
ok 1 加法函数的测试 1 加 1 应该等于 2
11
ok 2 加法函数的测试 任何数加0应该等于自身
ok 3 乘法函数的测试 1 乘 1 应该等于 1
11
ok 4 加法函数的测试 1 加 1 应该等于 2
11
ok 5 加法函数的测试 任何数加0应该等于自身
# tests 5
# pass 5
# fail 0

注意点:Babel默认不会对 Iterator, Generator, Promise, Map, Set等全局对象,以及一些全局对象的方法(比如Object.assign)转码,
如果我们想要对这些对象转码,我们需要安装 babel-polyfill.

npm install --save-dev babel-polyfill

最后,需要在我们的脚本头部加上 如下引入 babel-polyfill代码

import 'babel-polyfill'

八,异步测试
先在mocha项目目录下 新建文件demo3,如下目录结构:

demo3
  |---- timeout.test.js

timeout.test.js代码如下:

var expect = require('chai').expect;

describe('timeout.test.js - 超时测试', function() {
  it('测试应该 5000 毫秒后结束', function(done) {
    var x = true;
    var f = function() {
      x = false;
      expect(x).to.be.not.ok;
      done();
    };
    setTimeout(f, 4000);
  });
});

然后在demo3目录下,运行命令行 mocha timeout.test.js, 执行如下:

~/个人demo/vue1204/mocha/demo3 on  Dev_20171115_wealth!
$ mocha timeout.test.js

  timeout.test.js - 超时测试
    1) 测试应该 5000 毫秒后结束


  0 passing (2s)
  1 failing

  1) timeout.test.js - 超时测试
       测试应该 5000 毫秒后结束:
     Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.

可以看到如上报错 Timeout of 2000ms exceeded, 这是因为mocha默认每个测试用例最多执行2000毫秒,如果超过这个时间没有返回结果,就会报错,
所以我们在进行异步操作的时候,需要额外指定timeout的时间的。因为异步的操作是需要4000毫秒,所以我们指定5000毫秒就不会报错了。
如下命令:

mocha --timeout 5000 timeout.test.js

如下执行结果:

~/个人demo/vue1204/mocha/demo3 on  Dev_20171115_wealth! 
$ mocha --timeout 5000 timeout.test.js

  timeout.test.js - 超时测试
    ✓ 测试应该 5000 毫秒后结束 (4008ms)

  1 passing (4s)

这样就保证测试用例成功了。

Mocha内置对Promise的支持,允许直接返回Promise. 在demo3目录下 新建 promise.test.js, 如下目录结构:

demo3
  |---- timeout.test.js
  |---- promise.test.js

promise.test.js 代码如下:

var fetch = require('node-fetch');
var expect = require('chai').expect;

describe('promise异步测试', function() {
  it('异步请求应该返回一个对象', function() {
    return fetch("https://api.github.com")
      .then(function(res) {
        return res.json()
      }).then(function(json) {
        expect(json).to.be.an("object");
      })
  })
});

然后执行命令如下:

~/个人demo/vue1204/mocha/demo3 on  Dev_20171115_wealth!
$ mocha promise.test.js


  promise异步测试
    ✓ 异步请求应该返回一个对象 (1165ms)


  1 passing (1s)

如上可以看到也是可以成功的。

九:测试用例的钩子
Mocha在describe块之中,提供了测试用例的四个钩子,before(), after(), beforeEach()和afterEach(),他们会在指定的时间内执行。

代码如下:

describe('hooks', function() {
  before(function(){
    // 在本区块的所有测试用例之前执行
  });
  after(function(){
    // 在本区块的所有测试用例之后执行
  });
  beforeEach(function(){
    // 在本区块的每个测试用例之前执行
  });
  afterEach(function(){
    // 在本区块的每个测试用例之后执行
  });
});

before(): 将会在所有测试用例执行之前运行,比如在之前插入数据等等操作。
after(): 会在所有测试执行之后运行,用于清理测试环境,回滚到清空数据状态。
beforeEach(): 将会在每个测试用例执行之前执行,可用于测试测试需要准备相关数据的条件。
afterEach(): 将会在每个测试用例之后执行,可用于准备测试用例所需的后置条件。
请看如下demo,在来理解下 mocha的四个钩子函数,
在项目的根目录下 新建demo4,目录结构如下:

demo4
  |---- src
  |  |-- hooks.js
  |---- test
  |  |--- hooks.test.js

hooks.js 代码如下:

// 保存用户对象
var saveUserObj = {};

// 定义用户类
function User (name) {}

// 保存用户
User.save = function(name) {
  saveUserObj[name] = name;
} 

// 删除用户
User.delete = function(name) {
  delete saveUserObj[name];
}

// 检查是否包含该用户
User.contains = function(name) {
  return saveUserObj[name] !== null;
}
// 返回所有的数据
User.getUsers = function() {
  return saveUserObj;
}
module.exports = User;

hooks.test.js代码如下:

var should = require('should');
var User = require('../src/hooks.js');

// 描述User的行为
describe('描述User的行为', function(){
  // 执行所有测试之前,执行before函数,添加数据
  before(function(){
    User.save('kongzhi111');
    console.log(User.getUsers()); // 打印出{kongzhi111: 'kongzhi111'}
    console.log(111111111111111111111);
  });
  // 在执行每个测试前,执行beforeEach函数,添加数据
  beforeEach(function() {
    User.save('kongzhi222');
    console.log(User.getUsers());
    // 打印出 {kongzhi111: 'kongzhi111', kongzhi222: 'kongzhi222'}
    console.log(222222222222222222222222)
  })
  // 描述User.save的行为
  describe('描述User.save的行为', function() {
    // 保存kongzhi333成功了
    it('保存kongzhi333成功了', function() {
      User.save('kongzhi333');
      console.log(User.getUsers());
      // 打印出 {kongzhi111: 'kongzhi111', kongzhi222: 'kongzhi222', kongzhi333: 'kongzhi333'}
      console.log(33333333333333333333);
    })
  });
  // 描述User.contains的行为
  describe('描述User.contains的行为', function(){
    it('kongzhi111是存在的', function(){
      User.contains('kongzhi111').should.be.exactly(true);
    });
    it('kongzhi222是存在的', function(){
      User.contains('kongzhi222').should.be.exactly(true);
    });
    it('kongzhi333是存在的', function(){
      User.contains('kongzhi333').should.be.exactly(true);
    });
    it('kongzhi555是不存在', function(){
      User.contains('kongzhi555').should.be.exactly(true);
    });
  });
  // 在执行完每个测试后,清空数据
  afterEach(function() {
    User.delete('kongzhi222');
    console.log(User.getUsers());  // 打印 {kongzhi111: 'kongzhi111', kongzhi333: 'kongzhi333'}
    console.log(44444444444444444444444);
  });
  // 在执行完每个测试后,清空数据
  after(function() {
    User.delete('kongzhi111');
    console.log(User.getUsers()); // 打印 {kongzhi333: 'kongzhi333'}
    console.log(555555555555555555555555);
    User.delete('kongzhi333');
    console.log(User.getUsers()); // 打印 {}
  });
})

在demo4下 运行mocha,执行命令后 如下:

~/个人demo/vue1204/mocha/demo4 on  Dev_20171115_wealth!
$ mocha 

  描述User的行为
{ kongzhi111: 'kongzhi111' }
111111111111111110000
    描述User.save的行为
{ kongzhi111: 'kongzhi111', kongzhi222: 'kongzhi222' }
2.2222222222222222e+23
{ kongzhi111: 'kongzhi111',
  kongzhi222: 'kongzhi222',
  kongzhi333: 'kongzhi333' }
33333333333333330000
      ✓ 保存kongzhi333成功了
{ kongzhi111: 'kongzhi111', kongzhi333: 'kongzhi333' }
4.4444444444444445e+22
    描述User.contains的行为
{ kongzhi111: 'kongzhi111',
  kongzhi333: 'kongzhi333',
  kongzhi222: 'kongzhi222' }
2.2222222222222222e+23
      ✓ kongzhi111是存在的
{ kongzhi111: 'kongzhi111', kongzhi333: 'kongzhi333' }
4.4444444444444445e+22
{ kongzhi111: 'kongzhi111',
  kongzhi333: 'kongzhi333',
  kongzhi222: 'kongzhi222' }
2.2222222222222222e+23
      ✓ kongzhi222是存在的
{ kongzhi111: 'kongzhi111', kongzhi333: 'kongzhi333' }
4.4444444444444445e+22
{ kongzhi111: 'kongzhi111',
  kongzhi333: 'kongzhi333',
  kongzhi222: 'kongzhi222' }
2.2222222222222222e+23
      ✓ kongzhi333是存在的
{ kongzhi111: 'kongzhi111', kongzhi333: 'kongzhi333' }
4.4444444444444445e+22
{ kongzhi111: 'kongzhi111',
  kongzhi333: 'kongzhi333',
  kongzhi222: 'kongzhi222' }
2.2222222222222222e+23
      ✓ kongzhi555是不存在
{ kongzhi111: 'kongzhi111', kongzhi333: 'kongzhi333' }
4.4444444444444445e+22
{ kongzhi333: 'kongzhi333' }
5.5555555555555555e+23
{}

可以看到如上测试结果后的运行,试着理解一下,应该可以理解mocha中的4各钩子函数的含义了。

理解异步钩子函数

在demo4目录下的test文件下 新建 hooks-async.test.js 用于测试异步的代码

demo4
  |---- src
  |  |-- hooks.js
  |---- test
  |  |--- hooks.test.js
  |  |--- hooks-async.test.js

hooks-async.test.js 代码如下:

var expect = require('chai').expect;
describe('异步钩子函数', function() {
  var foo = false;
  beforeEach(function(){
    setTimeout(function(){
      foo = true;
    }, 50)
  });
  it('异步钩子函数成功', function() {
    expect(foo).to.be.equal(true);
  })
});

运行结果如下:

异步钩子函数
       异步钩子函数成功:

      AssertionError: expected false to equal true
      + expected - actual

      -false
      +true

如上可以看到测试失败,原因是因为setTimeout 是异步的,在setTimeout执行完之前,it函数已经被执行了,所以foo当时数据还是false,
因此false不等于true了。

这时候 done参数出来了,在回调函数存在时候,它会告诉mocha,你正在编写一个异步测试,会等到异步测试完成的时候来调用done函数。
或者超过2秒后超时,如下代码就可以成功了;
hooks-async.test.js 代码如下:

var expect = require('chai').expect;
describe('异步钩子函数', function() {
  var foo = false;
  beforeEach(function(done){
    setTimeout(function(){
      foo = true;
      // complete the async beforeEach
      done();
    }, 50)
  });
  it('异步钩子函数成功', function() {
    expect(foo).to.be.equal(true);
  });
});

10. 理解测试用例的管理
一个脚本中可能有很多测试用例,有时候,我们想只运行其中的几个,这时候我们使用only方法。describe块和it块都允许调用only方法,
表示只运行某个测试套件或测试用例。
在项目中新建文件demo5,结构如下:

demo5
  |---- src
  |  |-- add.js
  |---- test
  |  |--- add.test.js

add.test.js 代码如下:

var expect = require('chai').expect;
var add = require('../src/add.js');
it.only('1 加 1应该等于2', function() {
  expect(add(1, 1)).to.be.equal(2);
});

it('任何数加0应该等于自身', function() {
  expect(add(1, 0)).to.be.equal(1);
});

进入demo5目录,运行mocha命令后,如下:

~/个人demo/vue1204/mocha/demo5 on  Dev_20171115_wealth!
$ mocha


  ✓ 1 加 1应该等于2

  1 passing (8ms)

可以看到只运行了 only方法。

十一:浏览器测试
除了在命令行运行,mocha还可以在浏览器下运行。
首先,使用 mocha init 命令在在指定的目录生成初始化文件。
mocha init demo6
在mocha目录下 运行上面的命令后,会在demo6生成 index.html. mocha.css, mocha.js 和 tests.js 文件。
index.html代码如下:

<!DOCTYPE html>
<html>
  <head>
    <title>Mocha</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="mocha.css" />
  </head>
  <body>
    <div id="mocha"></div>
    <script src="mocha.js"></script>
    <script>mocha.setup('bdd');</script>
    <script src="tests.js"></script>
    <script>
      mocha.run();
    </script>
  </body>
</html>

然后在demo6 下 新建一个 src/add.js 文件
代码如下:

function add(x, y) {
  return x + y;
}

然后,把这个文件,以及断言库chai.js,加入index.html。

代码如下:

<!DOCTYPE html>
<html>
  <head>
    <title>Mocha</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="mocha.css" />
  </head>
  <body>
    <div id="mocha"></div>

    <!-- 浏览器新加的测试代码 -->
    <script type="text/javascript" src="src/add.js"></script>
    <script type="text/javascript" src="http://chaijs.com/chai.js"></script>

    <script src="mocha.js"></script>
    <script>mocha.setup('bdd');</script>
    <script src="tests.js"></script>
    <script>
      mocha.run();
    </script>
  </body>
</html>

然后在 tests.js 添加如下代码:

var expect = chai.expect;

describe('加法函数的测试', function() {
  it('1 加 1 应该等于 2', function() {
    expect(add(1, 1)).to.be.equal(2);
  });

  it('任何数加0等于自身', function() {
    expect(add(1, 0)).to.be.equal(1);
    expect(add(0, 0)).to.be.equal(0);
  });
});

运行index.html 即可看到效果。

十二: 生成规格文件
Mocha支持从测试用例生成规格文件。
在mocha-demo项目内 新建demo7文件,该目录文件存放如下文件
如下目录页面:

demo7
   |---- src
   |  |--- add.js
   |  |--- multiple.js
   |---- test
   |  |--- dir
   |  | |--- multiple.test.js 
   |  |
   |  |--- add.test.js

进入demo7目录,运行如下命令:

$ mocha --recursive -R markdown >spec.md

就会在该目录下 生成 spec.md 文件,-R markdown参数指定规格报告是markdown格式。
如果想生成HTML格式的报告spec.html,使用下面的命令。

$ mocha --recursive -R doc > spec.html

就会在该目录下 生成 spec.html文件。

git上查看demo源码