• mass Framework spec模块


    spec模块是我框架的测试模块,基于javascript 测试工具abut v3,它本身只依赖于核心模块dom.js与其样式表文件spec.css。下面是其JS源码:

    //==================================================
    // 测试模块
    //==================================================
    (function(global,DOC){
        var dom = global[DOC.URL.replace(/(#.+|\W)/g,'')];
        dom.define("spec", function(){
            //模块为dom添加如下方法:
            //quote isEqual dump Deferred runTest addTestModule
            //在全局命名空间下多添加一个函数 expect
            dom.mix(dom,{
                //在字符串两端加上引号,并对其内部一些字符进行转义,用于JSON与引用
                quote : String.quote ||  (function(){
                    var meta = {
                        '\t':'t',
                        '\n':'n',
                        '\v':'v',
                        'f':'f',
                        '\r':'\r',
                        '\'':'\'',
                        '\"':'\"',
                        '\\':'\\'
                    },
                    reg = /[\x00-\x1F\'\"\\\u007F-\uFFFF]/g,
                    regFn = function(c){
                        if (c in meta) return '\\' + meta[c];
                        var ord = c.charCodeAt(0);
                        return ord < 0x20   ? '\\x0' + ord.toString(16)
                        :  ord < 0x7F   ? '\\'   + c
                        :  ord < 0x100  ? '\\x'  + ord.toString(16)
                        :  ord < 0x1000 ? '\\u0' + ord.toString(16)
                        : '\\u'  + ord.toString(16)
                    };
                    return function (str) {
                        return    '"' + str.replace(reg, regFn)+ '"';
                    }
                })(),
                //比较对象是否相等或相似
                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 "XMLHttpRequest" :
                        case "Window" :
                            return indent + "[object "+type +"]";
                        case "NodeList":
                        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 ( i in obj) {
                                arr.push(next + self(i) + ": " + self(obj[i], next).replace(/^\s+/g, ""));
                            }
                            return indent + "{\n" + arr.join(",\n") + "\n" + indent + "}";
                    }
                }
    
            });
    
            //===============================异步列队模块===============================
            var Deferred = dom.Deferred = function (fn) {
                return this instanceof Deferred ? this.init(fn) : new Deferred(fn);
            }
            dom.mix(Deferred, {
                get:function(obj){//确保this为Deferred实例
                    return  obj instanceof Deferred ? obj : new Deferred;
                },
                ok : function (r) {
                    return r;
                },
                ng : function (e) {
                    throw  e;
                }
            });
            //http://www.adequatelygood.com/2010/7/Writing-Testable-JavaScript
            //http://www.dustindiaz.com/javascript-cache-provider/
            //http://d.hatena.ne.jp/uupaa/20100708
            //http://ajaxian.com/archives/ben-and-dion-step-down-as-editors-of-ajaxian-com
            Deferred.prototype = {
                init:function(fn){
                    this._firing = [];
                    this._fired = [];
                    if(typeof fn === "function")
                        return this.then(fn)
                    return this;
                },
                _add:function(okng,fn){
                    var obj = {
                        ok:Deferred.ok,
                        ng:Deferred.ng,
                        arr:[]
                    }
                    if(typeof fn === "function")
                        obj[okng] = fn;
                    this._firing.push(obj);
                    return this;
                },
                _fire:function(okng,args,result){
                    var type = "ok",
                    obj = this._firing.shift();
                    if(obj){
                        this._fired.push(obj);
                        var self = this;
                        if(typeof obj === "number"){//如果是延时操作
                            var timeoutID = setTimeout(function(){
                                self._fire(okng,self.before(args,result))
                            },obj)
                            this.onabort = function(){
                                clearTimeout(timeoutID );
                            }
                        }else if(obj.arr.length){//如果是并行操作
                            var i = 0, async;
                            while(async = obj.arr[i++]){
                                async.fire(args)
                            }
                        }else{//如果是串行操作
                            try{
                                result = obj[okng].apply(this,args);
                            }catch(e){
                                type = "ng";
                                result = e;
                            }
                            this._fire(type,this.before(args,result))
                        }
                    }else{//队列执行完毕,还原
                        (this.after || dom.noop)(result);
                        this._firing = this._fired;
                        this._fired = [];
                    }
                    return this;
                },
                then:function(fn){
                    return  Deferred.get(this)._add("ok",fn)
                },
                once:function(fn){
                    return  Deferred.get(this)._add("ng",fn)
                },
                fire:function(){
                    return this._fire("ok",this.before(arguments));
                },
                error:function(){
                    return this._fire("ng",this.before(arguments));
                },
                wait:function(timeout){
                    var self = Deferred.get(this);
                    self._firing.push(timeout)
                    return self
                },
                abort:function(){
                    (this.onabort || dom.noop)();
                    return this;
                },
                //每次执行用户回调函数前都执行此函数,返回一个数组
                before:function(args,result){
                    return result ? result instanceof Array ? result : [result] : dom.slice(args)
                },
                //并行操作,并把所有的子线程的结果作为主线程的下一个操作的参数
                paiallel : function (fns) {
                    var self = Deferred.get(this),
                    obj = {
                        ok:Deferred.ok,
                        ng:Deferred.ng,
                        arr:[]
                    },
                    count = 0,
                    values = {}
                    for(var key in fns){
                        if(fns.hasOwnProperty(key)){
                            (function(key,fn){
                                if (typeof fn == "function"){
                                    fn = Deferred(fn);
                                }
                                fn.then(function(value){
                                    values[key] = value;
                                    if(--count <= 0){
                                        if(fns instanceof Array){
                                            values.length = fns.length;
                                            values = dom.slice(values);
                                        }
                                        self._fire("ok",[values])
                                    }
                                }).once(function(e){
                                    self._fire("ng",[e])
                                });
                                obj.arr.push(fn);
                                count++
                            })(key,fns[key])
                        }
                    }
                    self.onabort = function(){
                        var i = 0, d;
                        while(d = obj.arr[i++]){
                            d.abort();
                        }
                    }
                    self._firing.push(obj);
                    return self
                },
                loop : function (obj, fn, complete,result) {
                    obj = {
                        begin : obj.begin || 0,
                        end   : (typeof obj.end == "number") ? obj.end : obj - 1,
                        step  : obj.step  || 1,
                        last  : false,
                        prev  : null
                    }
                    var step = obj.step,
                    _loop = function(i,obj){
                        if (i <= obj.end) {
                            if ((i + step) > obj.end) {
                                obj.last = true;
                                obj.step = obj.end - i + 1;
                            }
                            obj.prev = result;
                            result = fn.call(obj,i);
                            Deferred.get(result).then(_loop).fire(i+step,obj);
                        }else{
                            if(typeof complete === "function"){
                                return complete.call(null,result)
                            }
                            return result;
                        }
                    }
                    return (obj.begin <= obj.end) ? Deferred.get(this).then(_loop).fire(obj.begin,obj) : null;
                }
            }
            "loop wait then once paiallel".replace(/\w+/g, function(method){
                Deferred[method] = Deferred.prototype[method];
            });
            //===================================其他辅助方法============================
            var $ = function(id) {
                return DOC.getElementById(id);
            };
            var toHTML = function() {
                var div = DOC.createElement("div");
                return function(html) {
                    div.innerHTML = html;
                    return div.firstChild;
                };
            }();
    
            //在字符串嵌入表达式 http://www.cnblogs.com/rubylouvre/archive/2011/03/06/1972176.html
            var reg_format = /\\?\#{([^{}]+)\}/gm;
            var format = function(str, object){
                var array = dom.slice(arguments,1);
                return str.replace(reg_format, function(match, name){
                    if (match.charAt(0) == '\\')
                        return match.slice(1);
                    var boolIndex = Number(name)
                    if(boolIndex >=0 )
                        return array[boolIndex]
                    if(object && object[name])
                        return  object[name]
                    return  '' ;
                });
            }
            var Expect = function(actual){
                return this instanceof Expect ? this.init(actual) : new Expect(actual);
            }
            function getUnpassExpect(str){
                var boolIndex = 1,ret = "error!",section = 0, qualifier = "("
                for(var j=1;j < str.length;j++){
                    if(str.charAt(j) == "("){
                        boolIndex++
                    }else if(str.charAt(j) == ")"){
                        boolIndex--
                    }else if(str.charAt(j) != qualifier && boolIndex == 0){
                        section++
                        if(section == 1){
                            qualifier = ")"//取得expect(...)中的部分
                            boolIndex = -1
                        }else if(section == 2){
                            boolIndex = 1;//取得ok,eq,match,log等函数名
                            qualifier = ")"
                        }else if(section == 3){//取得最后的函数体,并返回整个匹配项
                            ret = "expect" + str.slice(0,j)
                            break
                        }
                    }
                }
                return ret;
            }
            dom.mix(Expect,{
                refreshTime : function(){//刷新花费时间
                    $("dom-spec-time").innerHTML = new Date - Expect.START_IIME;
                },
                //渲染结果,这里是其最上面的数值统计栏,从左到右分别是失败数,错误数,成功通过的测试占总测试样例的比例值,
                //测试所耗的毫秒数及当前测试的浏览器
                runTest:function(){
                    if($("dom-spec-result") ){
                        return
                    }
                    var html = ['<div id="dom-spec-result"><p class="dom-spec-summary">',
                    '<span id="dom-spec-failures" title="0">0</span> failures ',
                    '<span id="dom-spec-errors" title="0">0</span> errors ',
                    '<span id="dom-spec-done" title="0">0</span>% done ',
                    '<span id="dom-spec-time" title="0">0</span>ms </p>',
                    '<p class="dom-spec-summary">',navigator.userAgent,
                    '</p><div id="dom-spec-cases"><div><div>'];
                    DOC.body.appendChild(toHTML(html.join("")));
                    //当实际测试文件数与期待测试的文件数相等时,我们才开始测试
                    Expect.START_IIME = new Date;//记录测试的开始时间
                    Expect.refreshTime();//更新毫秒数
                    D.paiallel(Expect.queue).fire();//开始测试
                },
                CLASS : {
                    0:"dom-spec-unpass",
                    1:"dom-spec-pass",
                    2:"dom-spec-error"
                },
                queue : [],
    
                prototype:{
                    init:function(actual){//传入一个目标值以进行比较或打印
                        this.actual = actual;
                        return this;
                    },
                    ok:function(){//判定是否返回true
                        this._should("ok");
                    },
                    ng:function(){//判定是否返回false
                        this._should("ng");
                    },
                    log:function(msg){//不做判断,只打印结果,用于随机数等肉眼验证
                        this._should("log",msg);
                    },
                    eq:function(expected){//判定目标值与expected是否全等
                        this._should("eq", expected);
                    },
                    match:function(fn){//判定目标值与expected是否全等
                        this._should("match", fn);
                    },
                    not:function(expected){//判定目标值与expected是否非全等
                        this._should("not", expected);
                    },
                    has:function(prop){//判定目标值是否包含prop属性
                        this._should("has", prop);
                    },
                    contains:function(el){//判定目标值是否包含el这个元素(用于数组或类数组)
                        this._should("contains", el);
                    },
                    same: function(expected){//判定结果是否与expected相似(用于数组或对象或函数等复合类型)
                        this._should("same", expected);
                    },
                    _should:function(method,expected){//上面方法的内部实现,比较真伪,并渲染结果到页面
                        var actual = this.actual,bool = false;
                        if(method != "log"){
                            Expect.boolIndex++;
                        }
                        Expect.totalIndex++
                        switch(method){
                            case "ok"://布尔真测试
                                bool = actual === true;
                                expected = true;
                                break;
                            case "ng"://布尔非测试
                                bool = actual === false;
                                expected = false;
                                break;
                            case "eq"://同一性真测试
                                bool = actual == expected;
                                break;
                            case "not"://同一性非测试
                                bool = actual != expected;
                                break;
                            case "same":
                                bool = dom.isEqual(actual, expected);
                                break
                            case "has":
                                bool = Object.prototype.hasOwnProperty.call(actual, expected);
                                break;
                            case "match":
                                bool = expected(actual);
                                break;
                            case "contains":
                                for(var i = 0,n = actual.length; i < n ;i++ ){
                                    if(actual === expected ){
                                        bool = true;
                                        break;
                                    }
                                }
                                break;
                            case "log":
                                bool = "";
                                Expect.Client.appendChild(toHTML('<pre class="dom-spec-log" title="log">'+(expected||"")+"  "+dom.dump(actual)+'</pre>'));
                                break;
                        }
                        //修改统计栏的数值
                        var done = $("dom-spec-done");
                        var errors = $("dom-spec-errors");
                        var failures = $("dom-spec-failures");
                        if(typeof bool === "boolean"){
                            Expect.PASS = ~~bool;
                            if(!bool){//如果没有通过
                                failures.title++;
                                failures.innerHTML = failures.title;
                                var statement = getUnpassExpect((Expect.expectArr[Expect.totalIndex] || ""))
                                var html = ['<div class="dom-spec-diff clearfix"><p>本测试套件中第',Expect.boolIndex,
                                '条测试出错: ',statement,'</p><div>actual:<pre title="actual">'+dom.type(actual)+" : "+dom.dump(actual)+'</pre></div>',
                                '<div>expected:<pre title="expected">'+dom.type(expected)+" : "+dom.dump(expected)+'</pre></div>',
                                '</div>'];
                                Expect.Client.appendChild(toHTML(html.join('')));
                            }
                            done.title++;
                            done.innerHTML = (((done.title-errors.title-failures.title)/done.title)*100).toFixed(0);
                        }
                    }
                }
            });
            dom.bind(DOC,"click",function(e){
                var target = e.target || e.srcElement;
                if(target.tagName === "A"){
                    var parent = target.parentNode.parentNode;
                    if(parent.className== "dom-spec-case"){//用于切换详情面板
                        var ul = parent.getElementsByTagName("ul")[0];
                        var display = ul.style.display;
                        ul.style.display = display === "none" ? "block" : "none";
                    }
                }
            });
            //shortcut
            var D = dom.Deferred;
            dom.runTest = Expect.runTest
            //暴露到全局作用域
            global.expect = Expect;
            
            dom.addTestModule = function(title, cases) {    
                //===============================生成测试模块===========================
                var module = function(){     
                    return function(){
                        var moduleId = "dom-spec-"+title, keys = [], length = 0;
                        if(!$(moduleId)){
                            /**   =================每个模块大抵是下面的样子===============
                        <div class="dom-spec-case" id="dom-spec-dom.js">
                        <p><a href="javascript:void(0)">JS文件名字</a></p>
                        <ul style="display: none;" class="dom-spec-detail">
                        测试结果
                        </ul>
                        </div>
                             */
                            var html = ['<div id="#{0}" class="dom-spec-case">',
                            '<p class="dom-spec-slide"><a href="javascript:void(0)">#{1}</a></p>',
                            '<ul class="dom-spec-detail" style="display:none;"></ul></div>'].join('');
                            $("dom-spec-cases").appendChild(toHTML(format(html, moduleId, title)));
                           
                        }
                        for(var i in cases){//取得describe第二个参数的那个对象所包含的所有函数,并放到异步列队中逐一执行它们
                            if(cases.hasOwnProperty(i)){
                                keys.push(i);
                                length++;
                            }
                        }
                        D.loop(length,function(i){
                            var name = keys[i],suite = cases[name],caseId = "dom-spec-case-"+name.replace(/\./g,"-");
                            if(!$(caseId)){//对应一个方法
                                var parentNode = $(moduleId).getElementsByTagName("ul")[0];
                                //显示测试样例
                              
                                var safe = (suite+"").replace(/</g,"<").replace(/>/g,">");
                                Expect.expectArr = safe.split("expect");
                                  
                                //函数体本身
                                var node = toHTML(format('<li id="#{0}">#{1}<pre>#{2}</pre></li>',caseId,name,safe));
                                parentNode.appendChild(node);
                            }
                            Expect.Client = $(caseId);
                            Expect.PASS = 1;//用于判定此测试套件有没有通过
                            Expect.boolIndex = 0;//用于记录当前是执行到第几条测试
                            Expect.totalIndex = 0;
                            try{
                                suite();//执行测试套件
                            }catch(err){
                                Expect.PASS = 2;
                                var htm = ["第",Expect.boolIndex,"行测试发生错误\n"];
                                for(var e in err){
                                    htm.push(e+" "+(err[e]+"").slice(0,100)+"\n");
                                }
                                htm = '<pre title="error">'+htm.join("").replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')+"</pre>";
                                Expect.Client.appendChild(toHTML(htm));
                                var errors = $("dom-spec-errors");
                                errors.title++;
                                errors.innerHTML = errors.title;
                            }
                            $(caseId).className = Expect.CLASS[Expect.PASS];
                            Expect.refreshTime();//更新测试所花的时间
                            return D.wait(32);
                        },function(){
                            Expect.refreshTime(); //结束测试
                        }).fire();
                    }
                }(title,cases);
                
                Expect.queue.push(module);
           
            } 
        })
    })(this,this.document);
    //2011.7.24 by 司徒正美
    //2011.7.28
    //添加Expect.prototype.match方法,并重构Expect的实例方法的定义
    //2011.8.4
    //修正Expect实例的ok,ng这两个方法的bug
    //2011.8.9
    //增加getUnpassExpect函数,用于取得没有通过的expect并显示出来
    

    样式表文件为:

    @CHARSET "UTF-8";
    
    #dom-spec-result {
        border:5px solid #00a7ea;
        padding:10px;
        background:#03c9fa;
        list-style-type:none;
    }
    .dom-spec-summary {
        height: 2em;
        line-height: 2em;
        margin: 0;
        font-size: 13px;
        font-weight: bold;
        text-indent: 2em;
        background:#008000;
        color:#fff;
    }
    .dom-spec-detail{
        list-style: none;
        margin: 0;
        padding: 0;
    }
    .dom-spec-detail li{
        margin: 0;
        padding:0;
        border: 2px solid #03c9fa;
        text-indent: 1em;
    }
    .dom-spec-pass{
        background:#a9ea00;
    }
    .dom-spec-unpass{
        background:#cd0000;
        color:#fff;
    }
    .dom-spec-detail pre{
        margin: 1em;
        text-indent: 0;
        font-style: normal;
        background: #F0F8FF;
        padding: 2px;
        color:#000;
        border:2px outset #c0c0c0;
    }
    .dom-spec-error{
        background: #000;
        color:#fff;
    }
    .dom-spec-log{
        background: #cc9!important;
    }
    /*用于点击展开*/
    .dom-spec-slide {
        background:#a9ea00;
        text-indent: 2em;
        line-height: 1.4em;
        height: 1.4em;
        margin: 0;
    }
    .dom-spec-diff {
        background: red;
        margin: 1em;
    }
    .dom-spec-diff div{
        45%;
        float: left;
    }
    .dom-spec-diff pre{
        background: #00cc00;
    }
    /* new clearfix */
    .clearfix:after {
        visibility: hidden;
        display: block;
        font-size: 0;
        content: " ";
        clear: both;
        height: 0;
    }
    * html .clearfix             { zoom: 1; } /* IE6 */
    *:first-child+html .clearfix { zoom: 1; } /* IE7 */
    

    spec会在dom对象上新添加一些方法以扩展其功能,同时还暴露了一个叫expect的方法到全局作用域下,通常情况下,模块是不会这样做,这个是例外,完全是出于调用方便的考虑。expect是整个测试系统的核心,它可以接受任何类型的参数,并返回一个Expect类的实例,进而让我们可以调用其一些方法,比较我们的期待值来判断对错。详情见注释。

    测试时,我们也要像建立模块那样组织测试,例如我们想测试一下核心模块里面的函数,则新建一个test_dom.js文件,内容如下:

    
    (function(global,DOC){
        var dom = global[DOC.URL.split("#")[0]];
        dom.define("test_dom","spec",function(){
            dom.addTestModule('测试核心模块-dom', {
                'type': function() {
                    expect(dom.type("string")).eq("String");
                    expect(dom.type(1)).eq("Number");
                    expect(dom.type(!1)).eq("Boolean");
                    expect(dom.type(NaN)).eq("NaN");
                    expect(dom.type(/test/i)).eq("RegExp");
                    expect(dom.type(dom.K())).eq("Function");
                    expect(dom.type(dom.K()())).eq("Undefined");
                    expect(dom.type(null)).eq("Null");
                    expect(dom.type({})).eq("Object");
                    expect(dom.type([])).eq("Array");
                    expect(dom.type(new Date)).eq("Date");
                    expect(dom.type(window)).eq("Window");
                    expect(dom.type(document)).eq("Document");
                    expect(dom.type(document.documentElement)).eq("HTML");
                    expect(dom.type(document.body)).eq("BODY");
                    expect(dom.type(document.childNodes)).eq("NodeList");
                    expect(dom.type(document.getElementsByTagName("*"))).eq("NodeList");
                    expect(dom.type(arguments)).eq("Arguments");
                    expect(dom.type(1,"Number")).eq(true);
                },
                "oneObject":function(){
                    expect(dom.oneObject("aa,bb,cc")).same({
                        "aa":1,
                        "bb":1,
                        "cc":1
                    });
                    expect(dom.oneObject([1,2,3],false)).same({
                        "1":false,
                        "2":false,
                        "3":false
                    });
                }
            });
        })
       
    })(this,this.document);
    
    

    然后建立一个body没有什么内容的HTML页面,引入核心模块,调用 dom.runTest()方法就行了。

    
    <!DOCTYPE html>
    <html>
      <head>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
        <title>dom Frameword 测试页面</title>
        <link href="/stylesheets/spec.css" rel="stylesheet" type="text/css"/>
        <script src="/neo/dom.js"></script>
        <script>
          dom.require("test_dom,test_lang", function(){
            dom.runTest();
          });
        </script>
    
      </head>
      <body></body>
    </html>
    
    

    链接可以打开,查看每个方法的详细测试结果。

    如果我们把最后的回调也当成模块,为它建立对应的测试模块,那么我们的所有方法都能得有效的测试,保证代码质量了!

  • 相关阅读:
    ppt 制作圆角三角形
    ROS 错误之 [rospack] Error: package 'beginner_tutorials' not found
    ubuntu下安装搜狗输入法以及出现不能输入中文的解决办法
    <crtdbg.h> 的作用
    mybatis配置
    POJO、Bean和JavaBean
    类类型与反射
    Spring层面的事务管理
    java项目常用架构
    java 遍历数组的几种方式
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/2115314.html
Copyright © 2020-2023  润新知