现在的位置: 首页 > 综合 > 正文

javascript 测试工具abut v3

2012年01月17日 ⁄ 综合 ⁄ 共 18248字 ⁄ 字号 评论关闭

新版本并没有添加新功能,只是修bug与进行代码重构!详细用法请看abut v2的介绍。

//abut v3 annotations-based unit testing,基于注释的单元测试工具 by 司徒正美
//http://www.cnblogs.com/rubylouvre/archive/2011/03/06/1972250.html
(function(WIN,DOM){
    var addEvent = (function () {
        if (DOM.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, WIN.event);
                });
            }
        }
    })();
    ////////////////////////////////////////////////////////////////////////////////////////////////
    var A_slice = Array.prototype.slice;
    var metaOne = {
        '\b': '\\b',
        '\t': '\\t',
        '\n': '\\n',
        '\f': '\\f',
        '\r': '\\r',
        '\\': '\\\\'
    }
    WIN.dom = WIN.dom || {
        mix: function(target, source ,override) {
            var i, ride = (override === void 0) || override;
            for (i in source) {
                if (ride || !(i in target)) {
                    target[i] = source[i];
                }
            }
            return target;
        }
    }
    dom.mix(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;
        },
        quote : String.quote || function (str) {
            str = str.replace(/[\x00-\x1f\\]/g, function (chr) {
                var special = metaOne[chr];
                return special ? special : '\\u' + ('0000'+chr.charCodeAt(0).toString(16)).slice(-4);
            });
            return '"' + str.replace(/"/g, '\\"') + '"';
        }
    },false);

    //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){}
        }
    })(WIN);
    ///////////////////////////////////////////////////////////////////////////
    var rcomments = /\/\/([^\r\n]+)|\/\*([\s\S]+?)\*\//g;
    var rltrim = /^\s+/
    var rsign  = /(\$|#){4}(\w*)\(([\s\S]+)\);?/
    if ( rltrim.test( "\xA0" ) ) {
        rltrim = /^[\s\xA0]+/;
    }
    var rstar = /^\*+/;//多行注释中的前置*号
    var rlt4  = /<<<</
    var rgt4  = />>>>/
    var rtest = /try{dom._should/
    var rCRFL = /\r?\n/ //回车符与换行符
    var ropacity = /opacity:\s*(\d?\.\d+)/g;
    var trimCode = function(source){
        var results = [],  flag_mul , flag_push, line, m, num = 0
        while((m = rcomments.exec(source))){//取得所有注释
            var comment =  m[1] || m[2] , lines = comment.split(rCRFL) , result = []
            for(var i=0, n = lines.length; i < n ;i++){
                line = lines[i].replace(rltrim,'').replace(rstar,' ');
                if(rlt4.test(line)){//在使用了夹具的情况,使用<<<<作为左界定符
                    line = "try{"
                    flag_mul = flag_push = true;
                }
                if( flag_mul){//夹具
                    flag_push = true
                }
                if( rgt4.test(line)){//在使用了夹具的情况,使用>>>>作为左界定符
                    flag_mul = false;
                    line = "}catch(e){};"
                    flag_push = true;
                }
                if(rtest.test(line)){//用于统计一共有多少个测试样例
                    num++
                    flag_push = true
                }
                if( flag_push ){
                    result.push(line)
                }
                flag_push = false;
            }
            if(/\S+/.test(result.join("")))
                results = results.concat(result)
        }
        return  {
            code:results.join("\n"),
            num:num
        }
    }
    //将源码分行并添加上行号
    var cleanCode = function (source) {
        var lines = source.split(rCRFL) ,line
        for(var i=0,n = lines.length; i < n ;i++){
            line = lines[i].replace(rltrim, '');
            lines[i] = line.replace(rsign,function(code,$1,$2,$3){
                if($1 == "####")
                    dom.abut.isModify = true;
                code = window.uneval && uneval(code) || dom.quote(code)
                return "try{dom._should("+$3+","+i+","
                + dom.quote($2 || "ok") +","+ code
                +')}catch(e){dom.abut.render("dom-abut-error","第'+i+'行测试代码发生错误",'+code+'+"<br/>"+ e)};'

            });
        }
        return lines.join('\n');
    };


    dom.mix(dom,{
        abut:function(str,appendTo,text){
            appendTo = appendTo || DOM.body
            if(str.indexOf("url(")=== 0){
                var url = str.slice(4,-1),
                xhr = dom.xhr();
                xhr.open("GET",url+"?"+(new Date-0),false);
                xhr.send(null);
                text = xhr.responseText || "";
                if (!text) throw "the target file does not exist";
            }else{
                var el = DOM.getElementById(str);
                if (!el) throw "can not find the target element";
                text = el.text;
            }
            var self = arguments.callee;
            self.isModify = false;
            var purified_code = cleanCode(text);
            var obj = trimCode(purified_code);
            if(!self._first){//保证这里的代码只执行一次
                self._first = true;
                el = self.componentElement = DOM.createElement("ul");
                el.className = "dom-abut-result";
                self.render("dom-abut-title",'<span>一共有'+obj.num+'个测试</span> <span id=dom-abut-pass>0</span>done  <span id=dom-abut-unpass>0</span>errors');
                appendTo.appendChild(el);
                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-result li blockquote{\
                    padding:5px;display:none;\
                }\
                .dom-abut-title{\
                    background:#008000;\
                }\
                .dom-abut-pass{\
                     background:#a9ea00;\
                }\
                .dom-abut-unpass{\
                     background:red;color:#fff;\
                }\
                .dom-abut-error{\
                     background:#000;color:#fff;\
                }\
                .dom-abut-log{\
                    background:#c0c0c0;\
                }\
                .dom-abut-log blockquote{\
                    font-weight: normal;background:#808080;\
                }");
                addEvent(el,"click",function(e){
                    e = e || WIN.event;
                    var target = e.target || e.srcElement;
                    var blockquote =  target.getElementsByTagName("blockquote")[0];
                    if(blockquote){
                        blockquote.style.display =  !!(blockquote.offsetHeight || blockquote.offestWidth) ? "none": "block";
                    }
                });
            }
            try {
                self.isModify && eval(purified_code);
                eval(obj.code);
            } catch (e) {
                self.render("dom-abut-error","解析编译测试代码失败!");
            }
        },
        addSheet : function(css,appendTo){
            var self = arguments.callee,style ,el = appendTo || dom.insertion || DOM.body
            if(!self.style){
                var styles = DOM.getElementsByTagName("style"), i = 0, reg = /screen|all/i, media
                while(style = styles[i++]){
                    media = style.getAttribute("media");
                    if(media === null || reg.test(media)){
                        self.style = style;
                        break;
                    }
                }
                if(!self.style){
                    style = DOM.createElement('style');
                    el.parentNode.insertBefore(style,el);
                    self.style = style;
                }
            }
            if(!-[1,] && el.filters){//IE6-8
                css = css.replace(ropacity,function($,$1){
                    $1 = parseFloat($1) * 100;
                    if($1 < 0 || $1 > 100)
                        return "";
                    return "filter:alpha(opacity="+ $1 +");"
                });
            }
            css += "\n";//增加末尾的换行符,方便在firebug下的查看。
            if(style.styleSheet){    //ie
                style.styleSheet.cssText += css;//添加新的内部样式
            }else if(WIN.Components){
                style.innerHTML += css;//火狐支持直接innerHTML添加样式表字串
            }else{
                style.appendChild(DOM.createTextNode(css))
            }
        },

        _should:function(actual, expected, line, method, source){
            var args = Array.apply([],arguments);
            source = args.pop();
            method = args.pop();
            line   = args.pop();
            if(args.length === 1){
                expected = void 0;
            }
            var bool, context
            switch(method){
                case "ok"://布尔真测试
                    bool = !!actual
                    break;
                case "ng"://布尔非测试
                    bool = !!!actual
                    break;
                case "eq"://同一性真测试
                    bool = actual == expected;
                    break;
                case "not"://同一性非测试
                    bool = actual != expected;
                    break;
                case "same":
                    bool = dom.isEqual(actual, expected)
                    break
                case "log":
                    bool = "<pre>"+dom.dump(actual)+"</pre>";
                    break;
            }

            if(typeof bool === "string"){
                context = '第' + line+"行日志记录";
                dom.abut.render("dom-abut-log",context,bool);
            }else{
                context =  '第'+ line+'行测试代码: '+(bool ? '通过' :'不通过' ) ;
                var id = bool ? 'dom-abut-pass' : 'dom-abut-unpass';
                var el = DOM.getElementById(id);
                var count = ~~el.innerHTML
                el.innerHTML = ++count;
                source = "actual    :   "+actual+"<br/> expected :   "+expected+"<hr/>"+source
                dom.abut.render(id,context,source);
            }
        },
        //比较对象是否相等或相似
        isEqual: function(a, b) {
            if (a === b) {
                return true;
            } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || dom.type(a) !== dom.type(b)) {
                return false; // don't lose time with error prone cases
            } else {
                switch(dom.type(a)){
                    case "String":
                    case "Boolean":
                    case "Number":
                    case "Null":
                    case "Undefined":
                        //处理简单类型的伪对象与字面值相比较的情况,如1 v new Number(1)
                        if (b instanceof a.constructor || a instanceof b.constructor) {
                            return a == b;
                        }
                        return a === b;
                    case "NaN":
                        return isNaN(b);
                    case "Date":
                        return  a.valueOf() === b.valueOf();
                    case "Array":
                        var len = a.length;
                        if (len !== b.length)
                            return false;
                        for (var i = 0; i < len; i++) {
                            if (!this.isEqual(a[i], b[i])) {
                                return false;
                            }
                        }
                        return true;
                    default:
                        for (var key in b) {
                            if (!this.isEqual(a[key], b[key])) {
                                return false
                            }
                        }
                        return true;
                }
            }
        },
        dump : 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 + dom.quote(obj);
                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 + "}";
            }
        }
    });

    dom.mix(dom.abut,{
        render : function(className,context,code){
            var li = DOM.createElement("li");
            li.className = className;
            this.componentElement.appendChild(li);
            var blockquote = DOM.createElement("blockquote")
            li.innerHTML = context;
            if(code){
                li.appendChild(blockquote);
                blockquote.innerHTML = code;
            }
        }
    });
 
})(this,this.document);

比如我页面上有一个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("test3",document.body);
       //v2的使用方法,太笨重,已废弃
       // dom.abut({selector:"test3", target:document.body });

如果我们想对远程的JS文件(只限于同域,因为使用XMLHttpRequest对象),则使用方法如下:

        dom.abut("url(http://aaa.js)",document.body);
       //v2的使用方法,太笨重,已废弃
       // dom.abut({url:"http://aaa.js", target:document.body });

另,第二个参数是可以,默认就是把测试结果添加到document.body的底部!

对复杂对象的查看:

     /*>>>
       */

    var flatten = function(arr) {
        var result = [],self = arguments.callee;
        for(var i=0,n=arr.length,el;i >>>
       */

例子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 

    

//abut v3 annotations-based unit testing,基于注释的单元测试工具 (function(WIN,DOM){ //http://tech.kayac.com/archive/javascript-test-framework-sweets.html var dom = WIN[escape(DOM.URL.split("#")[0])]; dom.declare("abut", ["lang","xhr"],function(){ try{ var reg_comments = /\/\/([^\r\n]+)|\/\*([\s\S]+?)\*\//g; var reg_ltrim = /^\s+/; var reg_sign = /(\$|#){4}(\w*)\(([\s\S]+)\);?/; if ( reg_ltrim.test( "\xA0" ) ) { reg_ltrim = /^[\s\xA0]+/; } var reg_star = /^\*+/;//多行注释中的前置*号 var reg_lt4 = />>>/; var reg_test = /try{dom._should/; var reg_CRFL = /\r?\n/; //回车符与换行符 var reg_opacity = /opacity:\s*(\d?\.\d+)/g; var trimCode = function(source){ var results = [], flag_mul , flag_push, line, m, num = 0; while((m = reg_comments.exec(source))){//取得所有注释 var comment = m[1] || m[2] , lines = comment.split(reg_CRFL) , result = []; for(var i=0, n = lines.length; i >>>作为左界定符 flag_mul = false; line = "}catch(e){dom.log(e)};" flag_push = true; } if(reg_test.test(line)){//用于统计一共有多少个测试样例 num++; flag_push = true; } if( flag_push ){ result.push(line); } flag_push = false; } if(/\S+/.test(result.join(""))){ results = results.concat(result); } } return { code:results.join("\n"), num:num } } //将源码分行并添加上行号 var cleanCode = function (source) { var lines = source.split(reg_CRFL) ,line; for(var i=0,n = lines.length; i "+ e)};' }); } return lines.join('\n'); }; //使用方法 : dom.abut("url(/dom/lang.js)") dom.mix(dom,{ abut:function(str,appendTo,/*private*/text){ appendTo = appendTo || DOM.body; if(str.indexOf("url(")=== 0){ var url = str.slice(4,-1); var xhr = dom.xhr(); xhr.open("GET",url+"?"+(new Date-0),false); xhr.send(null); text = xhr.responseText || ""; if (!text) throw "the target file does not exist"; }else{ var el = DOM.getElementById(str); if (!el) throw "can not find the target element"; text = el.text; } var self = arguments.callee; self.isModify = false; var purified_code = cleanCode(text); var obj = trimCode(purified_code); if(!self._first){//保证这里的代码只执行一次 self._first = true; el = self.componentElement = DOM.createElement("ul"); el.className = "dom-abut-result"; self.render("dom-abut-title",'一共有'+obj.num+'个测试0done  0errors'); appendTo.appendChild(el); 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-result li blockquote{\ padding:5px;display:none;\ }\ .dom-abut-title{\ background:#008000;\ }\ .dom-abut-pass{\ background:#a9ea00;\ }\ .dom-abut-unpass{\ background:red;color:#fff;\ }\ .dom-abut-error{\ background:#000;color:#fff;\ }\ .dom-abut-log{\ background:#c0c0c0;\ }\ .dom-abut-log blockquote{\ font-weight: normal;background:#808080;\ }"); dom.bind(el,"click",function(e){ e = e || WIN.event; var target = e.target || e.srcElement; var blockquote = target.getElementsByTagName("blockquote")[0]; if(blockquote){ blockquote.style.display = !!(blockquote.offsetHeight || blockquote.offestWidth) ? "none": "block"; } }); } try { self.isModify && eval(purified_code); eval(obj.code); } catch (e) { self.render("dom-abut-error","解析编译测试代码失败!"); } }, addSheet : function(css,appendTo){ var self = arguments.callee,style ,el = appendTo || dom.insertion || DOM.body; if(!self.style){ var styles = DOM.getElementsByTagName("style"), i = 0, reg = /screen|all/i, media; while(style = styles[i++]){ media = style.getAttribute("media"); if(media === null || reg.test(media)){ self.style = style; break; } } if(!self.style){ style = DOM.createElement('style'); el.parentNode.insertBefore(style,el); self.style = style; } } if(!+"\v1" && el.filters){//IE6-8 css = css.replace(reg_opacity,function($,$1){ $1 = parseFloat($1) * 100; if($1 100){ return ""; } return "filter:alpha(opacity="+ $1 +");"; }); } css += "\n";//增加末尾的换行符,方便在firebug下的查看。 if(style.styleSheet){ //ie style.styleSheet.cssText += css;//添加新的内部样式 }else if(WIN.Components){ style.innerHTML += css;//火狐支持直接innerHTML添加样式表字串 }else{ style.appendChild(DOM.createTextNode(css)); } }, _should:function(actual, expected, line, method, source){ var args = Array.apply([],arguments); source = args.pop(); method = args.pop(); line = args.pop(); if(args.length === 1){ expected = void 0; } var bool, context; switch(method){ case "ok"://布尔真测试 bool = !!actual; expected = actual; break; case "ng"://布尔非测试 bool = !!!actual; expected = actual; break; case "eq"://同一性真测试 bool = actual == expected; break; case "not"://同一性非测试 bool = actual != expected; break; case "same": bool = dom.isEqual(actual, expected); break case "log": bool = "
"+dom.dump(actual)+"

";
break;
}
if(typeof bool === "string"){
context = '第' + line+"行日志记录";
dom.abut.render("dom-abut-log",context,bool);
}else{
context = '第'+ line+'行测试代码: '+(bool ? '通过' :'不通过' ) ;
var id = bool ? 'dom-abut-pass' : 'dom-abut-unpass';
var el = DOM.getElementById(id);
var count = ~~el.innerHTML;
el.innerHTML = ++count;
source = "actual : "+actual+"
expected : "+ expected+"


"+source;
dom.abut.render(id,context,source);
}
},
//比较对象是否相等或相似
isEqual: function(a, b) {
if (a === b) {
return true;
} else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || dom.type(a) !== dom.type(b)) {
return false; // don't lose time with error prone cases
} else {
switch(dom.type(a)){
case "String":
case "Boolean":
case "Number":
case "Null":
case "Undefined":
//处理简单类型的伪对象与字面值相比较的情况,如1 v new Number(1)
if (b instanceof a.constructor || a instanceof b.constructor) {
return a == b;
}
return a === b;
case "NaN":
return isNaN(b);
case "Date":
return a.valueOf() === b.valueOf();
case "Array":
var len = a.length;
if (len !== b.length)
return false;
for (var i = 0; i

posted on 2011-03-06 14:39 司徒正美 阅读(...) 评论(...) 编辑 收藏

【上篇】
【下篇】

抱歉!评论已关闭.