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