学习测试框架Mocha
Mocha 是javascript测试框架之一,可以在浏览器和Node环境下使用,除了Mocha测试框架之外,类似的测试框架还有Jasmine, Karma, Tape等。
可以使用npm全局安装:如下命令:
npm install -g mocha
也可以作为项目的依赖进行安装,如下命令:
npm install --save-dev mocha
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; })