为了深入了解某些测试框架的工作原理,并在培训中构建一个简单的测试框架,我系统地研究了should.js的源代码。本文将分享我的学习过程和分析结果,帮助读者更好地掌握should.js的核心机制。
背景
为了研讨与进修某些测试框架的事情道理,同时也为了完成培训中完成一个简朴的测试框架的缘由,我对should.js的代码举行了进修与剖析,如今与人人来举行交流下。
目次
ext
assertion.js
assertion-error.js
config.js
should.js
util.js
个中ext
为文件夹,其他为js文件。
组织
个中should.js
为全部项目进口,asssertion.js
为should.js中的类,担任对测试信息举行纪录。assertion-error.js
为should.js定义了一个毛病类,担任存储毛病信息。config.js
中存储了一些should.js中的一些设置信息。util.js
中则定义了一些项目中经常运用的东西函数。
should.js
var should = function should(obj) {
return (new should.Assertion(obj));
};
should.AssertiOnError= require('./assertion-error');
should.Assertion = require('./assertion');
should.format = util.format;
should.type = require('should-type');
should.util = util;
should.cOnfig= require('./config');
exports = module.exports = should;
should.js
进口文件初始化了一个类,并将一切文件中其他的模块举行引入。同时将自身export出去,让自身能够被require到。
should.extend = function (propertyName, proto) {
propertyName = propertyName || 'should';
proto = proto || Object.prototype;
var prevDescriptor = Object.getOwnPropertyDescriptor(proto, propertyName);
Object.defineProperty(proto, propertyName, {
set: function () {
},
get: function () {
return should(util.isWrapperType(this) ? this.valueOf() : this);
},
configurable: true
});
return {
name: propertyName, descriptor: prevDescriptor, proto: proto};
};
should.js
自身定义了一个extend要领,用于兼容should.js的另一种挪用体式格局,即should(obj)
的体式格局即是should.js
的通例挪用体式格局obj.should
,从而兼容另一种写法。
should
.use(require('./ext/assert'))
.use(require('./ext/chain'))
.use(require('./ext/bool'))
.use(require('./ext/number'))
.use(require('./ext/eql'))
.use(require('./ext/type'))
.use(require('./ext/string'))
.use(require('./ext/property'))
.use(require('./ext/error'))
.use(require('./ext/match'))
.use(require('./ext/contain'));
should.js
中还定义了use要领,从而让我们能够自身编写一些范例推断比方isNumber等函数导入到项目中,从而轻易举行测试。项目目次中的ext
文件夹就是编写的一些简朴的should.js的扩大。背面将在引见扩大时对二者的事情道理以及运用要领举行引见。
assertion.js
function Assertion(obj) {
this.obj = obj;
//any标志位
//@type {boolean}
this.anyOne= false; //not标志位
//@type {boolean}
this.negate = false;
this.params = {actual: obj};
}
assertion.js
中定义了一个Assertion类,个中any为should.js中的any
要领的标志位,而not则为其not
要领的标志位。
Assertion.add = function(name, func) {
var prop = {enumerable: true, configurable: true};
prop.value = function() {
var cOntext= new Assertion(this.obj, this, name);
context.anyOne= this.anyOne;
try {
func.apply(context, arguments);
} catch(e) {
//check for fail
if(e instanceof AssertionError) {
//negative fail
if(this.negate) {
this.obj = context.obj;
this.negate = false;
return this;
}
if(context !== e.assertion) {
context.params.previous = e;
}
//positive fail
context.negate = false;
context.fail();
}
// throw if it is another exception
throw e;
}
//negative pass
if(this.negate) {
context.negate = true;//because .fail will set negate
context.params.details = 'false negative fail';
context.fail();
}
//positive pass
if(!this.params.operator) this.params = context.params;//shortcut
this.obj = context.obj;
this.negate = false;
return this;
};
Object.defineProperty(Assertion.prototype, name, prop);
};
assertion.js
中的add要领在Assertion的原型链中增加自定义定名的要领,从而让我们能够打包一些推断的要领来举行挪用,不须要反复举行代码的编写。该要领详细的运用体式格局我们在背面临扩大举行解说时将会提到。
Assertion.addChain = function(name, onCall) {
OnCall= onCall || function() {
};
Object.defineProperty(Assertion.prototype, name, {
get: function() {
onCall();
return this;
},
enumerable: true
});
};
addChain
要领增加属性到原型链中,该属性在挪用要领后返回挪用者自身。该要领在should.js
的链式挪用中起着主要的作用。
同时,Assertion类还支撑别号功用,alias
要领运用Object对象的getOwnPropertyDescriptor
要领来对属性是不是存在举行推断,并挪用defineProperty
举行赋值。
Assertion
类在原型链中定义了assert
要领,用来对各级限定前提举行推断。assert
要领与一般要领差别,它并未采纳参数来举行一些参数的通报,而是经由历程assert
要领地点的Assertion
对象的params
属性来举行参数的通报。因为在Assertion
对象中存储了相干的信息,运用这个要领来举行参数通报轻易在各级中assert
函数的挪用轻易。详细运用要领我们将在扩大的剖析时提到。
assert: function(expr) {
if(expr) return this;
var params = this.params;
if('obj' in params && !('actual' in params)) {
params.actual = params.obj;
} else if(!('obj' in params) && !('actual' in params)) {
params.actual = this.obj;
}
params.stackStartFunction = params.stackStartFunction || this.assert;
params.negate = this.negate;
params.assertion = this;
throw new AssertionError(params);
}
Assertion
类也定义了一个fail
要领能够让用户直接挪用从而抛出一个Assertion的Error。
fail: function() {
return this.assert(false);
}
assertion-error.js
在此文件中,定义了assertion中抛出来的毛病,同时在个中定义了一些信息存储的函数比方message
和detail
等,能够让毛病在被捕捉的时刻带上一些特定的信息从而轻易举行推断与处置惩罚。因为完成较为简朴,因而在此就不贴出代码,须要相识的人能够自身去查阅should.js的源码。
ext/bool.js
下面简朴引见一个Assertion
的扩大的事情体式格局。让我们能够对should.js的事情道理有一个越发深入的明白。
module.exports = function(should, Assertion) {
Assertion.add('true', function() {
this.is.exactly(true);
}); Assertion.alias('true', 'True');
Assertion.add('false', function() {
this.is.exactly(false);
});
Assertion.alias('false', 'False');
Assertion.add('ok', function() {
this.params = {operator: 'to be truthy'};
this.assert(this.obj);
});
};
//should.js
should.use = function (f) {
f(should, should.Assertion);
return this;
};
//use
'1'should.be.true();
经由历程上面的扩大模块代码以及should.js
文件中的use
函数,我们能够发明,use
函数向扩大模块传入了should
要领和Assertion
组织函数。在bool.js
这个扩大模块中,它经由历程挪用Assertion
对象上的add函数来增加新的推断体式格局,而且经由历程params
参数来关照Assertion
对象假如推断失利应当怎样提醒用户。
感受
should.js
怎样完成链式挪用?
在Assertion
类中,有一个addChain
要领,该要领为某些属性定义了一些在getter函数中挪用的操作要领,而且返回对象自身。经由历程这个要领,在ext/chain.js
中,它为should.js
中常见的语义词增加了属性,并经由历程返回对象自身来到达链式挪用的Assertion
对象通报。
['an', 'of', 'a', 'and', 'be', 'has', 'have', 'with', 'is', 'which', 'the', 'it'].forEach(function(name) {
Assertion.addChain(name);
});
以下两段代码在结果上是如出一辙的结果:
'1'.shoud.be.a.Number();
'1'.should.be.be.be.be.a.a.a.a.Number();
should.js
的完成体式格局有哪些值得自创的处所?
should.js
中,经由历程将一些语义词增加为属性值并返回Assertion
对象自身,因而有用处理了链式挪用的题目。
经由历程Asseriton
对象的属性来举行参数的通报,而不是经由历程函数参数,从而有用避免了函数挪用时参数的通报题目以及多层挪用时组织的庞杂。
should.js
经由历程扩大的体式格局来增加其推断的函数,保证了优越的扩大性,避免了代码耦合在一起,经由历程也为其他人编写更多的扩大代码供应了接口。
should.js
经由历程extend要领,让should(obj)
与obj.should
两种体式格局到达了雷同的结果。经由历程在defineProperty
中定义should属性而且在回调函数顶用should(obj)
的体式格局来猎取obj
对象。
经由历程抛出毛病而不是返回布尔值的体式格局来关照用户,能够越发显著的关照用户,也轻易向上抛出非常举行通报。
总结
总的来说,should.js
是一个比较小而精的测试框架,他能够满足在开辟历程中所须要的大部分测试场景,同时也支撑自身编写扩大来强化它的功用。在设想上,这个框架运用了不少奇妙的要领,避免了一些庞杂的链式挪用与参数通报等题目,而且组织清楚,比较合适举行浏览与进修。