综合众人的意见,此版本做了许多改进,如对注释抽取的优化,增加对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 >>>");
            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  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 第' + 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 >>>
       */

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

相关文章: