zoukankan      html  css  js  c++  java
  • javascript 测试工具abut发布

    abut全称为annotations-based unit testing,基于注释的单元测试工具,也可以就地取此英文的原义(毗邻)称呼它。众所周知,javascript实在不好做测试,即使我这个工具现在对事件响应这东西还是无可奈何的,这只能黑盒测试。不过,能白盒测试的,我们还是进行白盒测试。javascript经近几年的迅猛发展,也涌现诸如Qunit,JSpec这些优秀的测试框架。但我最后还是决定自己搞一个。原因如下:

    • 我喜欢自造轮子。
    • 由于在写框架(龟速进行中),倾向于选择器,测试工具等东西都出自自家。
    • 写文档是痛苦,倒不如写注释,既然写注释,就要物尽其用,一次性把注释与测试都写完。
    • 其他测试框架写测试都很恶心,单个测试的代码量比较长(本来就不想写,勉为其难地写,方法易用是王道)。
    • 其他测试框架写测试都是写在另一个文件上,更增加人写测试的抗拒性。
    • 写在另一个文件上,万一这文件丢失了怎么办?!

    顺便说一下单元测试的好处,缓解一下大家对它的厌恶。

    //http://www.cnblogs.com/nuaalfm/archive/2010/02/26/1674235.html
    //单元测试的优点
    //1、它是一种验证行为。 
    //    程序中的每一项功能都是测试来验证它的正确性。它为以后的开发提供支缓。就算是开发后期,我们也可以轻松的增加功能或更改程序结构,而不用担心这个过程中会破坏重要的东西。而且它为代码的重构提供了保障。这样,我们就可以更自由的对程序进行改进。
    //2、它是一种设计行为。 
    //    编写单元测试将使我们从调用者观察、思考。特别是先写测试(test-first),迫使我们把程序设计成易于调用和可测试的,即迫使我们解除软件中的耦合。
    //3、它是一种编写文档的行为。 
    //    单元测试是一种无价的文档,它是展示函数或类如何使用的最佳文档。这份文档是可编译、可运行的,并且它保持最新,永远与代码同步。
    //4、它具有回归性。 
    //    自动化的单元测试避免了代码出现回归,编写完成之后,可以随时随地的快速运行测试。
    

    基于上面的原因,我的单元测试与当前流行的测试框架有很大不同,首先测试代码与我们的执行代码是位于同一个文件,其次它是非常符号化的(汲取模板系统的经验),最后它总是对整个文件进行操作。为了获取注释,我是用AJAX的同步请求实现的(dom.abut(url))。

    现在说说一些相关概念。既然是单元测试,每个测试代码都应该封闭在一个独立的环境中,通常我们用闭包收拾之。但有可能连续几个测试程序都共有一个测试数据呢,但这测试数据当然也不能丢在全局作用域下,于是就有了大闭包与小闭包之分。具体表现如下:

    //第二个参数仅在浏览器支持Object.defineProperties时可用
    applyIf(Object,{
        create:function( proto, props ) {//ecma262v5 15.2.3.5
        //略去具体实现
        },
        //$$$$same(Object.keys({aa:1,bb:2,cc:3}),["aa","bb","cc"])
        keys: function(obj){//ecma262v5 15.2.3.14
        //略去具体实现
        }
    });
    
    //用于创建javascript1.6 Array的迭代器
    function iterator(vars, body, ret) {
        return eval('[function(fn,scope){'+
            'for(var '+vars+'i=0,l=this.length;i<l;i++){'+
            body.replace('_', 'fn.call(scope,this[i],i,this)') +
            '}' +
            ret +
            '}]')[0];
    };
    //注释照搬FF官网
    /*
    <<<<
    var arr = [1,2,3,4,5,6];
    $$$$eq(arr.indexOf(2),1)
    $$$$eq(arr.lastIndexOf(6),5)
    arr.slice(3).forEach(function(el,index,aaa){
        $$$$log(el,"item");
        $$$$log(index,"index");
        $$$$log(aaa,"array");
    });
    var arr2 = arr.map(function(el){
        return el+1;
    });
    $$$$same(arr2,[2,3,4,5,6,7]);
    >>>>
     **/
    applyIf(Array[PROTO],{
        //定位类 返回指定项首次出现的索引。
        indexOf: function (el, index) {
    //略去具体实现
        },
        //定位类 返回指定项最后一次出现的索引。
        lastIndexOf: function (el, index) {
    //略去具体实现
        },
    

    由<<<<与>>>>之间的注释我称之为大闭包,它圈着我们的测试程序与辅助函数与测试数据等,单行的以4个$开头的注释称之为小闭包。注释中的这些部分会被我的测试工具抽取出来进行加工执行。这里面涉及许多步骤,如$$$$会被替换为"dom.abut.",计算行号,统计当前执行到第几个测试程序,生成图形界面等等。既然是单元测试,就有assertTrue,assertFlase,assertEquals,assertSame等方法,不过这些方法有笨拙,Qunit简化为ok(布尔测试),equals(同值性测试),same(同一性测试)。我沿用Qunit的思路,依次为abut.ok,abut.eq,abut.same,当然我们在测试时,abut是用$$$$代替的。

    方法调用 说明 补充
    $$$$或$$$$ok 布尔测试
    $$$$eq 同值性测试 相等于a==b
    $$$$same 同一性测试 如果是简单类型则相等于===,array、object等比较其内容
    $$$$log 非测试debug用 相当于console.log

    对于AJAX,setTimeout等异步行为,我没有像Qunit那样搞个start与stop,大家看左上角的统计数字就知进行第几个测试程序了。注意,log是不统计到里面,虽然一样也显示在列表中。

    剩下一个问题,众所周知,单元测试都是针对公开的接口进行测试,像闭包内的函数怎么测试?为此,abut提供了专门的手段(@@@@)用于把它们偷渡到全局作用域下。当然,这不是真正意义的暴露,而是依附于我们的命名空间对象dom,放于一个叫exports的集中箱中,好让我们可以随时卸载它。

    (function(){
        var s = ["XMLHttpRequest",
        "ActiveXObject('Msxml2.XMLHTTP.6.0')",
        "ActiveXObject('Msxml2.XMLHTTP.3.0')",
        "ActiveXObject('Msxml2.XMLHTTP')",
        "ActiveXObject('Microsoft.XMLHTTP')"];
        if(dom.ie === 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){}
        }
        //偷渡s到全局作用域下
    //@@@@(s)
    })();
    //$$$$log(dom.exports.s);
    

    好了,比如我想测试我框架的两个模块:

    <!DOCTYPE HTML">
    <html>
        <head>
            <title></title>
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    		<script src="abut.js"></script>
    		<script>
      		window.onload = function(){
        			dom.abut("dom/lib/ecma.js");
        			dom.abut("dom/lib/brower.js");
      		}
    		</script>
            <title>dom Framework</title>
        </head>
        <body>
            
        </body>
    </html>
    

    只要在这些JS文件的注释中写好测试,当页面一载入,我们就可以看到效果!而且这些列表中的每一行都是可点的,点开查看详情。

    最后附上源码,我已经把它从我框架独立出来。

    // annotations-based unit testing by 司徒正美 
    // http://www.cnblogs.com/rubylouvre/archive/2010/11/02/1867655.html
    (function(){
        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) + '"';
                }
            })();
        }
        //http://www.cnblogs.com/rubylouvre/archive/2009/08/30/1556869.html
        var addSheet = function(css){
            if(!-[1,]){
                css = css.replace(/opacity:\s*(\d?\.\d+)/g,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 == 0){//如果不存在style元素则创建
                if(doc.createStyleSheet){    //ie
                    doc.createStyleSheet();
                }else{
                    style = doc.createElement('style');//w3c
                    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))
            }
        }
        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);
                    });
                }
            }
        })();
    
        this.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'){
                        if(obj===null) result = 'Null';
                        else if(obj.window==obj) result = 'Window'; //返回Window的构造器名字
                        else if(obj.callee) result = 'Arguments';
                        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 === "document"){//返回Document的构造器名字
                        result = "Document";
                    }
                    if(result === "Number" && isNaN(obj)){
                        result = "NaN";
                    }
                    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);
        //annotations-based unit testing 基于注释的测试系统 2010 10 31
        dom.abut = function(url){
            var xhr = dom.xhr();
            xhr.open("GET",url+"?"+(new Date-0),false);
            xhr.send(null);
            var text = xhr.responseText|| "";
            evalCode(text)
        }
        var rcomments = /\/\/([^\r\n]+)|\/\*([\s\S]+?)\*\//g;
        var rexports =  /[\/]{2,}@{4}\(([^\r\n]+)\);?/g;
        var r$$$$ = /(?:^|\s+)\$\$\$\$(\d+)(\w*)\(([^\r\n]+)\);?/g;
        //$$$$same(countOne,{ok:1, eq:1, same:1, '':1})
        var countOne = dom.oneObject(["ok","eq","same",""]);
        var fns = {
            ok:";\nabut.ok",
            eq:";\nabut.eq",
            same:";\nabut.same",
            log:";\nabut.log"
        } 
        var getAllComments = function(text){
            var m , result = [];
            while(m = rcomments.exec(text)){
                result.push(m[1] || m[2]);
            }
            return result.join('\n');
        };
        //构建闭包的开头部分
        var startClosure = function(index){
            return  "closures["+ index +"] =  function(){\n var abut = window.dom.abut\n"
        }
        //构建闭包的结束部分
        var endClosure = function(index,lineNumber){
            return  "};\nclosures["+ index+"].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.id)+
                fn +".lineNumber = " + $1 +  fn + ".testCode = " +  testCode.slice(1,-2).quote() + testCode +
                endClosure(obj.id,$1);
                if(countOne[$2])
                    obj.count++;
                obj.id++;
            });
            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.id) + str +  endClosure(obj.id,lineNumber);
            obj.id++
            arr.push(temp);
        }
        //添加行号以及暴露闭包中要测试中的数据到全局作用域下
        var cleanCode = function (source) {
            var lines = source.split( /\r?\n/) ;
            for(var i=0,n = lines.length; i < n ;i++){
                lines[i] = lines[i].replace(rexports,function($,$1){
                    dom.abut.isExports = true;
                    return ";dom.exports = dom.exports || {}; dom.exports["+ $1.quote()+"] = " + $1+";";
                });
                lines[i] = lines[i].replace(/\$\$\$\$|<<<</,function(str){
                    return str + (i+1)
                });
            }
            return lines.join('\n');
        };
        var evalCode = function(source){
            var abut = dom.abut;
            abut.ULID = "abut-"+(new Date - 0);
            abut.time = 0;
            abut.isExports = false;
            delete dom.exports;
            var uneval = cleanCode(source),arr = getAllComments(uneval).trim().split("<<<<"),
            i=0, n=arr.length, els,segment, resolving= ["var closures = window.dom.abut.closures = [];\n"],
            obj ={
                id:0,
                count:0
            }
            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;
            document.body.appendChild(UL);
            UL.className ="dom-abut-result";
            abut.render("dom-abut-title",'一共有'+obj.count+'个测试<span id="'+ abut.ULID+'"></span>');
            abut.recoder = document.getElementById( abut.ULID);
            addEvent(UL,"click",function(e){
                var target = e.target || e.srcElement;
                if(target.tagName ==="SPAN"){
                    var blockquote =  target.parentNode.getElementsByTagName("blockquote")[0];
                    if(blockquote){
                        blockquote.style.display =  !!(blockquote.offsetHeight || blockquote.offestWidth) ? "none": "block";
                    }
                }
            });
            //添加样式
            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 span{\
            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.isExports && 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) {
                    return abut.render("dom-abut-unpass","第"+fn.lineNumber +"行测试代码执行失败");
                }
            }
        }
        //功能类似于Qunit的ok 布尔判定
        dom.abut.ok = function(state){
            var bool = !!state,
            self = arguments.callee,
            lineNumber = self.lineNumber,
            testCode = self.testCode;
            this.prepareRender(bool,lineNumber,testCode);
        }
        //功能类似于Qunit的equals 可隐式转换的等号比较
        dom.abut.eq = function(actual, expected){
            var bool = actual == expected,
            self = arguments.callee,
            lineNumber = self.lineNumber,
            testCode = self.testCode;
            this.prepareRender(bool,lineNumber,testCode);
        }
        //功能类似于Qunit的same 用于比较复杂的数据类型
        dom.abut.same = function(actual, expected){
            var bool = dom.isEqual(actual, expected),
            self = arguments.callee,
            lineNumber = self.lineNumber,
            testCode = self.testCode;
            this.prepareRender(bool,lineNumber,testCode);
        }
        //相等于firefox中的console.log
        dom.abut.log = function(obj, message){
            var context = "<span>第" + arguments.callee.lineNumber+"行日志记录  "+ (message || "") + "</span>";
            var testCode = "<pre>"+dom.inspect(obj)+"</pre>";
            dom.abut.render("dom-abut-log",context,testCode);
        }
        dom.abut.prepareRender = function(bool,lineNumber,testCode){
            var className = bool ? 'dom-abut-pass' : 'dom-abut-unpass',
            context =  '<span>第'+ lineNumber+'行测试代码: '+(bool ? '通过' :'不通过' )+"</span>" ;
            this.recoder.innerHTML = "  已完成第"+(++this.time)+"个测试";
            this.render(className,context,testCode);
        }
        dom.abut.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;
            }
        }
        //用于比较对象
        dom.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;
        }
    //序列化对象(JSON.stringify对DOM对象无效,弃之)
        dom.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 + "}";
            }
        }
    
    })()
    
    
  • 相关阅读:
    14.4.9 Configuring Spin Lock Polling 配置Spin lock 轮询:
    14.4.8 Configuring the InnoDB Master Thread IO Rate 配置InnoDB Master Thread I/O Rate
    14.4.7 Configuring the Number of Background InnoDB IO Threads 配置 后台InnoDB IO Threads的数量
    14.4.7 Configuring the Number of Background InnoDB IO Threads 配置 后台InnoDB IO Threads的数量
    14.4.6 Configuring Thread Concurrency for InnoDB 配置Thread 并发
    14.4.6 Configuring Thread Concurrency for InnoDB 配置Thread 并发
    14.4.5 Configuring InnoDB Change Buffering 配置InnoDB Change Buffering
    14.4.5 Configuring InnoDB Change Buffering 配置InnoDB Change Buffering
    14.4.4 Configuring the Memory Allocator for InnoDB InnoDB 配置内存分配器
    14.4.4 Configuring the Memory Allocator for InnoDB InnoDB 配置内存分配器
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/1867655.html
Copyright © 2011-2022 走看看