QUnit源碼閱讀(3):asyncTest基本執行流程

//asyncTest, QUnit中的異步測試,具體參考QUnit官方文檔。
//直接上代碼
//step 1: write a simple asyncTest as the following.
asyncTest("asynchronous test: one second later!", function() {
    expect(1);

    setTimeout(function() {
        ok(true, "Passed and ready to resume!");
        start();
    }, 1000);
});


//step 2: 調用test函數
QUnit = {

    asyncTest: function( testName, expected, callback ) {
        if (arguments.length === 2) {
            callback = expected;
            expected = null;
        }

        QUnit.test(testName, expected, callback, true);
    },

    //...
    test : function(testName, expected, callback, async) {
        //...

        //初始化test,
        test = new Test({
            name : name,
            testName : testName,
            expected : expected,
            async : async, //☆☆☆ if asyncTest  then set async = true; else set async = undefined;
            callback : callback, //回調函數, very important
            module : config.currentModule,
            moduleTestEnvironment : config.currentModuleTestEnvironment,
            stack : sourceFromStacktrace(2)
        });

        if (!validTest(test)) {
            return;
        }

        test.queue();
    },
    //...
}

//step 3: 調用test.queue函數,在config.queue中存入test.init和queue的內置函數run。
Test.prototype = {
    //...
    queue : function() {
        var bad, test = this;

        synchronize(function() {
            test.init();
        });
        function run() {
            // each of these can by async
            synchronize(function() {
                test.setup();
            });
            synchronize(function() {
                test.run();
            });
            synchronize(function() {
                test.teardown();
            });
            synchronize(function() {
                test.finish();
            });
        }

        // `bad` initialized at top of scope
        // defer when previous test run passed, if storage is available
        bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-test-" + this.module + "-" + this.testName);

        if (bad) {
            run();
        } else {
            synchronize(run, true);
        }
    }
    //...
}

//step 4.1 - 4.2 在config.queue中存入callback函數
function synchronize(callback, last) {
    config.queue.push(callback);

    if (config.autorun && !config.blocking) {
        process(last);
    }
}

//step 5 調用Qunit.start函數
QUnit.load = function() {
    runLoggingCallbacks("begin", QUnit, {});
    //I do not care, right now

    //...
    QUnit.init();

    //...
    if (config.autostart) {
        QUnit.start();
    }
};



//step 6, 調用 process(true)
QUnit = {

    //...
    start : function(count) {
        config.semaphore -= count || 1;
        // don't start until equal number of stop-calls
        if (config.semaphore > 0) {
            return;
        }
        // ignore if start is called more often then stop
        if (config.semaphore < 0) {
            config.semaphore = 0;
        }
        // A slight delay, to avoid any current callbacks
        if (defined.setTimeout) {
            window.setTimeout(function() {
                if (config.semaphore > 0) {
                    return;
                }
                if (config.timeout) {
                    clearTimeout(config.timeout);
                }

                config.blocking = false;
                process(true);
            }, 13);
            //13ms後調用,即step 7
        } else {
            config.blocking = false;
            process(true);
        }
    },
    //...
} 


//step 7: 調用 process(true)
(function() {
    if (config.semaphore > 0) {
        return;
    }
    if (config.timeout) {
        clearTimeout(config.timeout);
    }

    config.blocking = false;
    process(true);
    //ref step 8
})()

//step 8, 循環泵,整個test框架的調用驅動函數,將config.queue[]中的回調依次拿出來run
//Attention: 利用函數window.setTimeout及next函數來不斷循環的技巧☆☆☆
function process(last) {
    function next() {
        process(last);
    }

    var start = new Date().getTime();
    config.depth = config.depth ? config.depth + 1 : 1;

    while (config.queue.length && !config.blocking) {
        if (!defined.setTimeout || config.updateRate <= 0 || ((new Date().getTime() - start ) < config.updateRate )) {
            config.queue.shift()();
            //Attention, this is very important.
        } else {
            window.setTimeout(next, 13);
            //Or, 13ms 之後再次調用process(true).
            break;
        }
    }
    config.depth--;
    if (last && !config.blocking && !config.queue.length && config.depth === 0) {
        done();
    }
}

//step 9, 調用init函數
Test.prototype = {
    //...
    init : function() {
        //...
    },
    //...
}

//step 10. 調用run函數, 在 config.queue中放置更多的函數test.setup, run, teardown, finish
Test.prototype = {
    //...
    queue : function() {
        //...
        function run() {
            // each of these can by async
            synchronize(function() {
                test.setup();
            });
            synchronize(function() {
                test.run();
            });
            synchronize(function() {
                test.teardown();
            });
            synchronize(function() {
                test.finish();
            });
        }

        //...
    }
};

//step 11. 依次調用test.setup, run, teardown, finish
Test.prototype = {
    //...
    setup : function() {
        //...
    },

    run : function() {
        config.current = this;

        var running = id("qunit-testresult");

        if (running) {
            running.innerHTML = "Running: <br/>" + this.name;
        }

        //sine this.async = true,QUnit.stop method will be invoked
        if (this.async) {
            QUnit.stop();
        }

        //this.callback 將會調用我們在 step1中寫的匿名函數function() { ok(1 == "1", "Passed!");})
        //this.callback 的初始化請參考step2
        if (config.notrycatch) {
            this.callback.call(this.testEnvironment, QUnit.assert);
            return;
        }

        try {
            this.callback.call(this.testEnvironment, QUnit.assert);
        } catch( e ) {
            QUnit.pushFailure("Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + e.message, extractStacktrace(e, 0));
            // else next test will carry the responsibility
            saveGlobal();

            // Restart the tests if they're blocking
            if (config.blocking) {
                QUnit.start();
            }
        }
    },

    teardown : function() {
        //...
    },

    finish : function() {
        //...
    },
};

//step 11.1
QUnit = {
    //...
    stop : function(count) {
        config.semaphore += count || 1;
        
        //Because, after set config.blocking = true, then the process method will not continue to shift
        //functions to run, it will only do an empty loop, please ref step 8. 
        config.blocking = true; // ☆☆☆ important, here, 
        

        if (config.testTimeout && defined.setTimeout) {
            clearTimeout(config.timeout);
            config.timeout = window.setTimeout(function() {
                QUnit.ok(false, "Test timed out");
                config.semaphore = 1;
                QUnit.start();
            }, config.testTimeout);
        }
    }
}; 
 

//step 12: 調用 我們的測試函數
(function() {
    expect(1);

    setTimeout(function() {
        ok(true, "Passed and ready to resume!");
        start(); //start function is important here.
    }, 1000);
})();

//step 12.1
QUnit = {
    //...
    // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
    expect: function( asserts ) {
        if (arguments.length === 1) {
            config.current.expected = asserts;
        } else {
            return config.current.expected;
        }
    },
};

//step 13: ok函數
QUnit.assert = {
    /**
     * Asserts rough true-ish result.
     * @name ok
     * @function
     * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
     */
    ok : function(result, msg) {
        //...
        result = !!result;
        //...
    },
    //...
}

//step 14, 調用start函數again
QUnit = {

    //...
    start : function(count) {
        //...
        // A slight delay, to avoid any current callbacks
        if (defined.setTimeout) {
            window.setTimeout(function() {
                if (config.semaphore > 0) {
                    return;
                }
                if (config.timeout) {
                    clearTimeout(config.timeout);
                }

                config.blocking = false; // ☆☆☆ important here, make the process function continue to work
                process(true);
            }, 13);
            //13ms後調用,即step 7
        } else {
            config.blocking = false; // ☆☆☆ important here, make the process function continue to work
            process(true);
        }
    },
    //...
} 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章