学习测试框架Mocha
注意:是参考阮老师的文章来学的。虽然阮老师有讲解,但是觉得自己敲一遍,然后记录一遍效果会更好点。俗话说,好记性不如烂笔头。
Mocha 是javascript测试框架之一,可以在浏览器和Node环境下使用,除了Mocha测试框架之外,类似的测试框架还有Jasmine, Karma, Tape等。
可以使用npm全局安装:如下命令:npm install -g mocha
也可以作为项目的依赖进行安装,如下命令:
npm install --save-dev mocha
如下所有的测试代码在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
如下是一些常用的比较;
// 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或falseexpect(true).to.be.true; expect(1).to.not.be.true;expect(false).to.be.false; expect(0).to.not.be.false;// null/undefined 断言目标是否为null/undefinedexpect(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/anexpect('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;// matchexpect('foobar').to.match(/^foo/); // exist 断言目标既不是null也不是undefinedvar 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 referencingvar obj = { foo: 'bar' }; expect(obj).to.have.property('foo'); expect(obj).to.have.property('foo', 'bar');// 类似于expect(obj).to.contains.keys('foo')// deep referencingvar 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 --help5.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!
我们可以使用模块,可以生成漂亮的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) 加法函数的测试11 ✓ 1 加 1 应该等于 211 ✓ 任何数加0应该等于自身 2 passing (2ms)
我在add.js 加了一句 console.log(11),上面可以看到也同样重新执行了 mocha命令。
5.4 --bail
--bail参数指定只要有一个测试用例没有通过,就停止执行后面的测试用例。mocha --bail5.5 --grep
--grep参数用于搜索测试用例的名称(即it块的第一个参数),然后只执行到匹配的测试用例。如下代码命令:$ mocha 加法函数的测试11 ✓ 1 加 1 应该等于 211 ✓ 任何数加0应该等于自身 2 passing (10ms)~/个人demo/vue1204/mocha/demo2 on Dev_20171115_wealth!$ mocha --grep "1 加 1" 加法函数的测试11 ✓ 1 加 1 应该等于 2 1 passing (12ms)
5.6 --invert
--invert参数表示只运行不符合条件的测试脚本,必须与 --grep参数配合使用。如下执行结果:
~/个人demo/vue1204/mocha/demo2 on Dev_20171115_wealth! $ mocha 加法函数的测试11 ✓ 1 加 1 应该等于 211 ✓ 任何数加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! $ mocha1..311ok 1 加法函数的测试 1 加 1 应该等于 211ok 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/register1..5(node:55513) DeprecationWarning: "--compilers" will be removed in a future version of Mocha; see https://git.io/vdcSr for more info11ok 1 加法函数的测试 1 加 1 应该等于 211ok 2 加法函数的测试 任何数加0应该等于自身ok 3 乘法函数的测试 1 乘 1 应该等于 111ok 4 加法函数的测试 1 加 1 应该等于 211ok 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代码如下:Mocha
然后在demo6 下 新建一个 src/add.js 文件
代码如下:function add(x, y) { return x + y;}
然后,把这个文件,以及断言库chai.js,加入index.html。
代码如下:
Mocha
然后在 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文件。