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); } }, //... }

你可能感兴趣的:(async)