// 直接上代码 //step 1: write a simple test as the following. test("hello test", function() { ok(1 == "1", "Passed!"); }); //step 2: 调用test函数 QUnit = { //... test : function(testName, expected, callback, async) { //... //初始化test, test = new Test({ name : name, testName : testName, expected : expected, async : async, 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; } 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 12: 调用 我们的测试函数 (function() { ok(1 == "1", "Passed!"); }); //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; //... }, //... equal : function(actual, expected, message) { QUnit.push(expected == actual, actual, expected, message); }, } extend( QUnit, { //... push: function( result, actual, expected, message ) { //... message = escapeInnerText( message ) || ( result ? "okay" : "failed" ); //... }, //... });
11-05