• 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 = '第' + arguments.callee.lineNumber+"行日志记录  "+ (message || "") + "";
                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 = '第'+ lineNumber+'行测试代码: '+(bool ? '通过' :'不通过' )+"" ; 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") ;
          }
    

  • 相关阅读:
    Gym
    HDU 5876 Sparse Graph(补图中求最短路)
    HDU 5873 Football Games(竞赛图兰道定理)
    HDU 5877 Weak Pair(树状数组+dfs+离散化)
    HDU 5963 朋友(找规律博弈)
    HDU 5961 传递
    POJ 3252 Round Numbers(数位dp)
    HDU 4734 F(x) (数位dp)
    HDU 2089 不要62(数位dp模板题)
    HDU 5936 Difference(折半搜索(中途相遇法))
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/1871458.html
Copyright © 2020-2023  润新知