javascript 测试工具 abut v2
综合众人的意见,此版本做了许多改进,如对注释抽取的优化,增加对script标签的支持,实时性的测试等等。
// dom.abut v2 (annotations-based unit testing by 司徒正美)
// http://www.cnblogs.com/rubylouvre/archive/2010/11/08/1868638.html
(function(){
//ecma262新扩展
if(!Object.keys){
var _dontEnum = ['propertyIsEnumerable', 'isPrototypeOf','hasOwnProperty','toLocaleString', 'toString', 'valueOf', 'constructor'];
for (var i in {
toString: 1
}) _dontEnum = false;
Object.keys = function(obj){//ecma262v5 15.2.3.14
var result = [],dontEnum = _dontEnum,length = dontEnum.length;
for(var key in obj ) if(obj.hasOwnProperty(key)){
result.push(key)
}
if(dontEnum){
while(length){
key = dontEnum[--length];
if(obj.hasOwnProperty(key)){
result.push(key);
}
}
}
return result;
}
}
if(!String.prototype.trim){
String.prototype.trim = function(){
return this.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
}
}
if(!String.prototype.quote){
String.prototype.quote = (function () {
var meta = {
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
}, reg = /[\\\"\x00-\x1f]/g,
regFn = function (a) {
var c = meta[a];
return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
};
return function(){
return '"' + this.replace(reg, regFn) + '"';
}
})();
}
var addEvent = (function () {
if (document.addEventListener) {
return function (el, type, fn) {
el.addEventListener(type, fn, false);
};
} else {
return function (el, type, fn) {
el.attachEvent('on' + type, function () {
return fn.call(el, window.event);
});
}
}
})();
var applyIf = function(target,source){
for(var name in source)
if(!target[name] ){
target[name] = source[name];
}
return target;
}
//释出命名空间对象
window.dom = window.dom || {};
applyIf(dom,{
// http://www.cnblogs.com/rubylouvre/archive/2010/01/20/1652646.html
type : (function(){
var reg = /^(\w)/,
regFn = function($,$1){
return $1.toUpperCase()
},
to_s = Object.prototype.toString;
return function(obj,str){
var result = (typeof obj).replace(reg,regFn);
if(result === 'Object' || (result === 'Function' && obj.exec) ){//safari chrome中 type /i/ 为function
if(obj===null) result = 'Null';//Object,Function,Null,Undefined,Window,Arguments等等都为其构造器名称
else if(obj.window==obj) result = 'Window';
else if(obj.callee) result = 'Arguments';
else if(obj.nodeType === 9) result = 'Document';
else if(obj.nodeName) result = (obj.nodeName+'').replace('#',''); //处理元素节点
else if(!obj.constructor || !(obj instanceof Object)){
if("send" in obj && "setRequestHeader" in obj){//处理IE5-8的宿主对象与节点集合
result = "XMLHttpRequest"
}else if("length" in obj && "item" in obj){
result = "namedItem" in obj ? 'HTMLCollection' :'NodeList';
}else{
result = 'Unknown';
}
}else result = to_s.call(obj).slice(8,-1);
}
if(result === "Number" && isNaN(obj)) result = "NaN";
//safari chrome中 对 HTMLCollection与NodeList的to_s都为 "NodeList",此bug暂时无解
if(str){
return str === result;
}
return result;
}
})(),
oneObject : function(array,val){
var result = {},value = val !== void 0 ? val :1;
for(var i=0,n=array.length;i < n;i++)
result[array[i]] = value;
return result;
}
});
//http://www.cnblogs.com/rubylouvre/archive/2010/04/20/1716486.html
(function(w,s){
s = ["XMLHttpRequest",
"ActiveXObject('Msxml2.XMLHTTP.6.0')",
"ActiveXObject('Msxml2.XMLHTTP.3.0')",
"ActiveXObject('Msxml2.XMLHTTP')",
"ActiveXObject('Microsoft.XMLHTTP')"];
if( !-[1,] && w.ScriptEngineMinorVersion() === 7 && location.protocol === "file:"){
s.shift();
}
for(var i = 0 ,el;el=s[i++];){
try{
if(eval("new "+el)){
dom.xhr = new Function( "return new "+el);
break;
}
}catch(e){}
}
})(window);
var rcomments = /\/\/([^\r\n]+)|\/\*([\s\S]+?)\*\//gm;
var rstar = /^(\s*)\*/gm;
var r$$$$ = /(?:^|\s+)\${4}(\d+)(\w*)\(([^\r\n]+)\);?/g;
var rwell = /[\/]{2,}\s*#{4}(\w*)\(([^\r\n]+)\)/;
var rdelimiter = /\${4}|<{4}/;
var ropacity = /opacity:\s*(\d?\.\d+)/g;
var fns = {
ok:";\nabut.ok",
eq:";\nabut.eq",
same:";\nabut.same",
log:";\nabut.log"
}
//只有以下方法才进行测试统计
var countOne = dom.oneObject(["ok","eq","same",""]);
//构建闭包的开头部分
var startClosure = function(arrayIndex){
return "closures["+ arrayIndex +"] = function(){\n var abut = window.dom.abut\n"
}
//构建闭包的结束部分
var endClosure = function(arrayIndex,lineNumber){
return "};\nclosures["+ arrayIndex+"].lineNumber = "+lineNumber+";\n";
}
//针对一条测试注释的小型闭包
var smartClosure = function(str,arr,obj){
var temp = "";
str.replace(r$$$$,function($,$1,$2,$3){
var fn = fns[$2] || fns.ok, testCode = fn + "("+$3+");\n";
temp += startClosure(obj.arrayIndex)+
fn +".lineNumber = " + $1 + fn + ".testCode = " + testCode.slice(1,-2).quote() + testCode +
endClosure(obj.arrayIndex,$1);
if(countOne[$2])
obj.count++;
obj.arrayIndex++;
});
arr.push(temp )
}
//针对多条测试注释的大型闭包
var bigClosure= function(str,arr,obj){
var lineNumber;
str = str.replace(/^\d+/,function(str){
lineNumber = parseInt(str,10) ;
return ""
});
str = str.trim().replace(r$$$$,function($,$1,$2,$3){
if(countOne[$2])
obj.count ++;
var fn = fns[$2] || fns.ok, testCode = fn + "("+$3+");\n";
return fn +".lineNumber = " + $1 + fn + ".testCode = " + testCode.slice(1,-2).quote() + testCode;
});
var temp = startClosure(obj.arrayIndex) + str + endClosure(obj.arrayIndex,lineNumber);
obj.arrayIndex++
arr.push(temp);
}
var modifyCode = function(method,textCode,lineNumber,obj){
if(countOne[method])
obj.count ++;
dom.abut.isModify = true;
var fn = fns[method] || fns.ok;
return "try{\n var abut = dom.abut;"+
fn+".lineNumber = " + lineNumber +";"+
fn+".testCode = " + textCode.quote() +";"+
fn+"("+textCode+");\n"+
"}catch(e){\n"+
'dom.but.render("dom-abut-unpass","第'+lineNumber+'行测试代码执行失败");\n}';
}
var cleanCode = function (source,obj) {
var lines = source.split( /\r?\n/) ,line ;
for(var i=0,n = lines.length; i < n ;i++){
line = lines[i].trim();
if(rwell.test(line)){
lines[i] = line.replace(rwell,function($,$1,$2){
return modifyCode($1,$2,i,obj);
})
}else{
lines[i] = lines[i].replace(rdelimiter,function(str){
return str + (i+1)
});
}
}
return lines.join('\n');
};
var evalCode = function(source,target){
var abut = dom.abut;
abut.ULID = "abut-"+(new Date-0).toString(36);
abut.time = 0;
abut.isModify = false;
var obj = dom.oneObject(["arrayIndex","count"],0), uneval = cleanCode(source,obj),
arr = dom.getComments(uneval).trim().split("<<<<"),
i=0, n=arr.length, els,segment, resolving= ["var closures = window.dom.abut.closures = [];\n"];
while(i < n){
segment = arr[i++];
els = segment.split(">>>>");
if(segment.indexOf(">>>>") !== -1){//这里不使用el.length === 2是为了避开IE的split bug
bigClosure(els[0],resolving,obj);
if(els[1]){
smartClosure(els[1],resolving,obj);
}
}else{
smartClosure(els[0],resolving,obj);
}
}
//构筑单元测试系统的UI
var UL = document.createElement("UL");
abut.el = UL;
target.appendChild(UL);
UL.className ="dom-abut-result";
abut.render("dom-abut-title",'一共有'+obj.count+'个测试');
abut.recoder = document.getElementById( abut.ULID);
if(!arguments.callee.first){//保证这里的代码只执行一次
arguments.callee.first = true;
addEvent(target,"click",function(e){
var target = e.target || e.srcElement;
if(target.className ==="dom-abut-slide"){
var blockquote = target.parentNode.getElementsByTagName("blockquote")[0];
if(blockquote){
blockquote.style.display = !!(blockquote.offsetHeight || blockquote.offestWidth) ? "none": "block";
}
}
});
//添加样式
dom.addSheet(".dom-abut-result {\
border:5px solid #00a7ea;\
padding:10px;\
background:#03c9fa;\
list-style-type:none;\
}\
.dom-abut-result li{\
padding:5px ;\
margin-bottom:1px;\
font-size:14px;\
}\
.dom-abut-slide{\
cursor: pointer;\
}\
.dom-abut-result li blockquote{\
margin:0;\
padding:5px;\
display:none;\
}\
.dom-abut-title{\
background:#008000;\
}\
.dom-abut-pass{\
background:#a9ea00;\
}\
.dom-abut-unpass{\
background:red;\
color:#fff;\
}\
.dom-abut-log{\
background:#c0c0c0;\
}\
.dom-abut-log blockquote{\
background:#808080;\
}");
}
try {
abut.isModify && eval(uneval);
eval(resolving.join(""));
} catch (e) {
return abut.render("dom-abut-unpass","解析编译测试代码失败");
}
for(var i=0,fn;fn= abut.closures[i++];){
try {
fn();
} catch (e) {
abut.render("dom-abut-unpass","第"+fn.lineNumber +"行测试代码执行失败");
}
}
}
applyIf(dom,{
abut:function(obj){
var key = obj.selector || obj.url,
target = obj.target || document.body,str;
if(dom.type(target,"String")){
target = document.getElementById(target);
}
if(obj.selector){
var el = document.getElementById(key);
if (!el) throw "can not find the target element";
str = el.text;
}else {
var xhr = dom.xhr();
xhr.open("GET",key+"?"+(new Date-0),false);
xhr.send(null);
str = xhr.responseText || "";
if (!str) throw "the target file does not exist";
}
evalCode(str,target)
},
getComments : function(text){
var m , result = [];
while(m = rcomments.exec(text)){
result.push((m[1] || m[2]).replace(rstar,function($,$1){
return $1
}));
}
return result.join('\n');
},
addSheet : function(css){
if(!-[1,]){
css = css.replace(ropacity,function($,$1){
$1 = parseFloat($1) * 100;
if($1 < 0 || $1 > 100)
return "";
return "filter:alpha(opacity="+ $1 +");"
});
}
css += "\n";//增加末尾的换行符,方便在firebug下的查看。
var doc = document, head = doc.getElementsByTagName("head")[0],
styles = head.getElementsByTagName("style"),style,media;
if(!styles.length){//如果不存在style元素则创建
style = doc.createElement('style');
style.setAttribute("type", "text/css");
head.insertBefore(style,null)
}
style = styles[0];
media = style.getAttribute("media");
if(media === null && !/screen/i.test(media) ){
style.setAttribute("media","all");
}
if(style.styleSheet){ //ie
style.styleSheet.cssText += css;//添加新的内部样式
}else if(doc.getBoxObjectFor){
style.innerHTML += css;//火狐支持直接innerHTML添加样式表字串
}else{
style.appendChild(doc.createTextNode(css))
}
},
//比较对象是否相等或相似
isEqual: function(a, b) {
if (a === b) return true;
var atype = typeof(a), btype = typeof(b);
if (atype != btype) return false;
if (a == b) return true;
if ((!a && b) || (a && !b)) return false;
if (a.isEqual) return a.isEqual(b);
if (dom.type(a,"Date") && dom.type(b,"Date")) return a.valueOf() === b.valueOf();
if (dom.type(a,"NaN") && dom.type(b,"NaN")) return false;
if (dom.type(a,"RegExp") && dom.type(b,"RegExp"))
return a.source === b.source &&
a.global === b.global &&
a.ignoreCase === b.ignoreCase &&
a.multiline === b.multiline;
if (atype !== 'object') return false;
if (a.length && (a.length !== b.length)) return false;
var aKeys = Object.keys(a), bKeys = Object.keys(b);
if (aKeys.length != bKeys.length) return false;
for (var key in a) if (!(key in b) || !dom.isEqual(a[key], b[key])) return false;
return true;
},
inspect : function(obj, indent) {
indent = indent || "";
if (obj === null)
return indent + "null";
if (obj === void 0)
return indent + "undefined";
if (obj.nodeType === 9)
return indent + "[object Document]";
if (obj.nodeType)
return indent + "[object " + (obj.tagName || "Node") +"]";
var arr = [],type = dom.type(obj),self = arguments.callee,next = indent + "\t";
switch (type) {
case "Boolean":
case "Number":
case "NaN":
case "RegExp":
return indent + obj;
case "String":
return indent + obj.quote();
case "Function":
return (indent + obj).replace(/\n/g, "\n" + indent);
case "Date":
return indent + '(new Date(' + obj.valueOf() + '))';
case "Unknown":
case "XMLHttpRequest" :
case "Window" :
return indent + "[object "+type +"]";
case "NodeList":
case "HTMLCollection":
case "Arguments":
case "Array":
for (var i = 0, n = obj.length; i < n; ++i)
arr.push(self(obj[i], next).replace(/^\s* /g, next));
return indent + "[\n" + arr.join(",\n") + "\n" + indent + "]";
default:
for (var i in obj) {
arr.push(next + self(i) + ": " + self(obj[i], next).replace(/^\s+/g, "") );
}
return indent + "{\n" + arr.join(",\n") + "\n" + indent + "}";
}
}
});
applyIf(dom.abut,{
//布尔测试
ok : function(state){
var bool = !!state,
self = arguments.callee,
lineNumber = self.lineNumber,
testCode = self.testCode;
this.prepareRender(bool,lineNumber,testCode);
},
//同值性测试
eq : function(actual, expected){
var bool = actual == expected,
self = arguments.callee,
lineNumber = self.lineNumber,
testCode = self.testCode;
alert("lineNumber" + lineNumber)
this.prepareRender(bool,lineNumber,testCode);
},
//同一性测试
same : function(actual, expected){
var bool = dom.isEqual(actual, expected),
self = arguments.callee,
lineNumber = self.lineNumber,
testCode = self.testCode;
this.prepareRender(bool,lineNumber,testCode);
},
log : function(obj, message){
var context = '";
var testCode = ""+dom.inspect(obj)+"
";
this.render("dom-abut-log",context,testCode);
},
prepareRender : function(bool,lineNumber,testCode){
var className = bool ? 'dom-abut-pass' : 'dom-abut-unpass',
context = '" ;
this.recoder.innerHTML = " 已完成第"+(++this.time)+"个测试";
this.render(className,context,testCode);
},
render : function(className,context,code){
var li = document.createElement("li");
li.className = className;
this.el.appendChild(li);
var blockquote = document.createElement("blockquote")
li.innerHTML = context;
if(code){
li.appendChild(blockquote);
blockquote.innerHTML = code;
}
}
});
})();
测试例子1:
var test1 = 1;
/*
* <<<<
* $$$$log(a)
* >>>>
*/
/* dsfds */
// frgtretr
/**
* erwwer
*
*/
测试例子2:
var p = function(){}
// comment /* not-a-nested-comment
p('not-a-comment'); // comment */* still-a-comment
p('not-a-comment'); /* alert('commented-out-code');
// still-a-comment */ p('not-a-comment');
var re= /\/* not-a-comment */; //* comment
abut v2增加了对script的支持,为此方法的参数由一个字符串改为一个哈希,哈希有三个值,selector,url与target。selector就是指script标签的id,通过来获取其innerHTML进行测试。url指JS文件的路径,我们通过同步AJAX请求获取responseText进行测试,由于是AJAX请求,就会受到同源策略的限制,因此v1我就无法放出示例,因此以后示例都使用selector方法。target是指将测试结果放到哪一个元素节点中,默认是document.body,此参数可选,并且如果是字符串,它会自动当成标签的ID进行寻找。
比如我页面上有一个id为test3的script元素节点,其innerHTML如下:
var Person = function(name,sex){
this.name = name;
this.sex = sex;
//####log(this.name);
//####log(this.sex);
}
var p = new Person("ruby","louvre");
想对其测试,只要引用abut.js,在页面上运行如下脚本即可:
dom.abut({
selector:id,//要测试的script标签
//url:url,//只限于同域的路径
target:document.body //把测试结果添加到的位置(可以是元素节点,也可以是其id值)
});
在此例子中,我们也可以看到新的标识符####,它将取代原先的@@@@。v1中的@@@@目的是将闭包中或内部函数中的数据释出全局作用域下,然后再用$$$$一系列方法对它进行测试。然后,实践证明,这有点麻烦,为何不直接在原先的内部作用域中执行它呢?!####就是基于此目的开发出来,相对应,@@@@就没有用了,被我废弃掉了,移除了。####会对源码进行修改再进行解释执行,而$$$$则先解析执行源码再对注释中的测试样例进行修葺后执行,执行时机是完全不一样的。$$$$可以跟eq,same,ok,log这四个函数名,####也完全一样,但####暂时不支持写在<<<<与>>>>之间。
例子4,对多行测试注释的使用。
var flatten = function(arr) {
var result = [],self = arguments.callee;
for(var i=0,n=arr.length,el;i < n;i++){
el = arr[i];
if (dom.type(el,"Array")) {
result = result.concat(self(el));
} else {
result.push(el);
}
}
return result;
}
/*<<<<
* var a =['frank', ['bob', 'lisa'], ['jill', ['tom', 'sally']]];
* $$$$same(flatten(a),['frank', 'bob', 'lisa', 'jill', 'tom', 'sally']);
* >>>>
*/
例子5(截取自我框架的base模块)
var
PROTO = "prototype",
CTOR = "constructor",
hasOwn = Object[PROTO].hasOwnProperty;
//用于取得数据的类型或判定数据的类型
// $$$$(dom.type(1,"Number"));
// $$$$(dom.type(NaN,"NaN"));
// $$$$(dom.type(void(0),"Undefined"));
// $$$$(dom.type("aaa","String"));
// $$$$(dom.type([1,2,3],"Array"));
// $$$$(dom.type(/i/,"RegExp"));
// $$$$(dom.type({},"Object"));
// $$$$(dom.type(document,"Document"));
// $$$$(dom.type(document.body,"BODY"));
// $$$$(dom.type(window,"Window"));
// $$$$(dom.type(true,"Boolean"));
// $$$$(dom.type(document.getElementsByTagName('script'),"HTMLCollection"));
dom.type = (function(){
var reg = /^(\w)/,
regFn = function($,$1){
return $1.toUpperCase()
},
to_s = Object[PROTO].toString;
return function(obj,str){
var result = (typeof obj).replace(reg,regFn);
if(result === 'Object' || (result === 'Function' && obj.exec) ){//safari chrome中 type /i/ 为function
if(obj===null) result = 'Null';
else if(obj.window==obj) result = 'Window'; //返回Window的构造器名字
else if(obj.callee) result = 'Arguments';
else if(obj.nodeType === 9) result = 'Document';
else if(obj.nodeName) result = (obj.nodeName+'').replace('#',''); //处理元素节点
else if(!obj.constructor || !(obj instanceof Object)){
if("send" in obj && "setRequestHeader" in obj){//处理IE5-8的宿主对象与节点集合
result = "XMLHttpRequest"
}else if("length" in obj && "item" in obj){
result = "namedItem" in obj ? 'HTMLCollection' :'NodeList';
}else{
result = 'Unknown';
}
}else result = to_s.call(obj).slice(8,-1);
}
if(result === "Number" && isNaN(obj)) result = "NaN";
//safari chrome中 对 HTMLCollection与NodeList的to_s都为 "NodeList",此bug暂时无解
if(str){
return str === result;
}
return result;
}
})()
//生成键值统一的对象,用于高速化判定
// $$$$same(dom.oneObject(["aa","bb","cc"]),{"aa":1,"bb":1,"cc":1})
dom.oneObject = function(array,val){
var result = {},value = val !== void 0 ? val :1;
for(var i=0,n=array.length;i < n;i++)
result[array[i]] = value;
return result;
},
// $$$$eq(dom.isPlainObject({}),true)
// $$$$eq(dom.isPlainObject({aa:"aa",bb:"bb",cc:"cc"}),true)
// $$$$eq(dom.isPlainObject(new Object),true)
// $$$$eq(dom.isPlainObject(window),false)
// $$$$eq(dom.isPlainObject(document.body),false)
dom.isPlainObject = function (obj){
return !!obj && dom.type(obj,"Object") && hasOwn.call(obj[CTOR][PROTO],"isPrototypeOf") ;
}
浙公网安备 33010602011771号