前些天在项目中由于要用到单元测试,包装了一个基于seajs的Qunit单元测试框架,看了下Qunit的源码,发现Qunit直接支持CommonJs模式的封装:
如是,直接移植到seajs下
define(function(require, exports, module) {
require('tests/qunit/qunit.css');
//Qunit Code
});
接下去做基于需求的外层封装:
几个要求,要有直接控制台log功能,有多次注入测试功能:
如下进行封装(assert.js)
define(function(require, exports, module) {
var $ = require('lib/jquery');
var q = require('tests/qunit/qunit');
//开启log
var enableLog = function(){
var logs = ["begin", "testStart", "testDone", "log", "moduleStart", "moduleDone", "done"];
for (var i = 0; i < logs.length; i++) {
(function() {
var log = logs[i];
q.QUnit[log] = function() {
console.log(log, arguments);
};
})();
}
};
var _test = function(conf){
var settings = {};
var defaults = {
enableLog : false,
unitTest : $.noop
};
settings = $.extend(true,{}, defaults, conf);
if(settings.enableLog) {
enableLog.call(this);
}
if(settings.unitTest) {
if(!$.isArray(settings.unitTest)){
settings.unitTest = [settings.unitTest];
}
for(var i = 0, len = settings.unitTest.length; i < len; ++i){
settings.unitTest[i].call(this, q);
}
}
};
//一次性run
exports.run = function(conf){
q.QUnit.reset();
_test.call(this, conf);
q.QUnit.load();
};
//开启log
exports.enableLog = enableLog;
//注入前最好reset一下
exports.reset = q.QUnit.reset;
//多次注入testcase
exports.test = _test;
//注入case后要load一下
exports.load = q.QUnit.load;
});
这样一个经过封装的assert断言module就好了。可以进行log输出进多次的单元测试注入。
接下来来一个分发(dispatch)module的单元测试案例:
组件的代码:
define(function(require, exports, module){
var $ = require('lib/jquery');
var _ = require('lib/underscore');
var _url = '/notice/tips4web.json';
var _parameter = {};
var _timer = 60000;
var events = [];
var timeoutFlag = null;
var _fire = function(data){
for(var i=0,l=events.length;i<l;i++){
try{
events[i].fn(data, events[i].arg);
}catch(e){
continue;
}
}
//log(events)
};
exports.update = function(op){
_parameter = op;
};
exports.refresh = function(timer){
_timer = timer || 60000;
if(!!timeoutFlag){
clearInterval(timeoutFlag);
}
var ajaxing = false;
timeoutFlag = setInterval(function(){
if(ajaxing){
return;
}
ajaxing = true;
$.ajax({
global:false,
url : _url,
data : _parameter,
_complete:function(){
ajaxing = false;
},
_success:function(data){
_fire(data);
}
});
}, _timer);
};
exports.subscribe = function(fn,arg){
var id = $.now();
events.push({
id:id,
fn:fn,
arg:arg
});
return id;
};
exports.unsubscribe = function(id){
if(!events || !id){
return false;
}
for(var i=0,l=events.length;i<l;i++){
if(events[i].id === id){
events.splice(i,1);
return true;
}
}
return false;
};
exports.clear = function(){
_parameter = {};
events = [];
};
exports.stop = function(){
if(!!timeoutFlag){
clearInterval(timeoutFlag);
}
};
exports.fire = _fire;
});
这个组件是一个消息中间件:
下面我们测试的要点是测试接口可用性和异步通信的触发及销毁:
define(function(require, exports, module) {
var assert = require('tests/qunit/assert');
var $ = require('lib/jquery');
var rm = require('module/reminder/reminder-middleware');
var _unitTest = function(q){
var isLogin = true;
var ajaxing;
q.module("reminder 接口测试");
q.asyncTest("没有参数group时", function() {
ajaxing = true;
$.ajax({
global:false,
url:'/notice/tips4web.json',
_complete:function(){
ajaxing = false;
},
_success:function(data){
q.equal( typeof(data['storyCount']) , 'undefined', '期望:"storyCount"未定义' );
q.ok(true, "数据返回成功");
q.start();
},
_failure: function(errors){
var errorInfo = errors[0].msg;
q.ok(false, errorInfo);
q.start();
}
});
});
q.asyncTest("主墙reminder", function() {
ajaxing = true;
$.ajax({
global:false,
url:'/notice/tips4web.json',
data : {'group': 'main'},
_complete:function(){
ajaxing = false;
},
_success:function(data){
q.notEqual( typeof(data['storyCount']) , 'undefined', '期望:"storyCount"有定义' );
if(typeof(data['storyCount']) !== 'undefined'){
q.ok(true, "数据返回成功");
q.start();
}
},
_failure: function(errors){
var errorInfo = errors[0].msg;
q.ok(false, errorInfo);
q.start();
}
});
});
q.asyncTest("有人说reminder", function() {
ajaxing = true;
$.ajax({
global:false,
url:'/notice/tips4web.json',
data : {'group': 'incoming'},
_complete:function(){
ajaxing = false;
},
_success:function(data){
q.notEqual( typeof(data['storyCount']) , 'undefined', '期望:"storyCount"有定义' );
if(typeof(data['storyCount']) !== 'undefined'){
q.ok(true, "数据返回成功");
q.start();
}
}
});
});
q.module("reminder 触发器测试");
var rid = null;
var _doSuccess = function(data){
q.ok(true, "数据返回成功");
q.start();
};
q.asyncTest("触发器创建并刷新", function() {
rid = rm.subscribe(function(data, arg){
_doSuccess.call(this, data);
rm.stop();
q.test('注销触发器',function(){
q.ok(rm.unsubscribe(rid),'注销触发器成功');
_updateTest.call(this, q);
});
},{});
rm.refresh(1000);
});
};
var _updateTest = function(q){
q.module("reminder 触发器消息装换测试");
var rmid = null;
var isFirst = true;
var _doReminderSuccess = function(data){
if(isFirst){
q.equal( typeof(data['storyCount']) , 'undefined', '没有参数时,期望:"storyCount"未定义' );
q.ok(true, "数据返回成功");
rm.update({'group': 'incoming'});
isFirst = false;
q.ok(rm.unsubscribe(rmid),'注销触发器成功');
q.start();
q.asyncTest("触发器URL更新并fetch", function() {
rmid = rm.subscribe(function(data, arg){
_doReminderSuccess.call(this, data);
},{});
});
}else{
q.notEqual( typeof(data['storyCount']) , 'undefined', 'Update为 incoming后,期望:"storyCount"有定义' );
q.ok(true, "数据返回成功");
rm.stop();
q.start();
}
};
q.asyncTest("触发器创建并刷新", function() {
rmid = rm.subscribe(function(data, arg){
_doReminderSuccess.call(this, data);
},{});
rm.refresh(1000);
});
};
exports.run = function(conf){
//开启log
assert.enableLog();
//直接run一个test,run只能出现一次,建议全部封装到unitTest中再一次性调用,适合单个调用
assert.run({
unitTest : _unitTest
});
};
});
最终效果:
最后还有一个问题:对于接口本地化测试会有跨域的问题,由于只是接口测试,不涉及兼容性,所以我们可以只用chrome测试,比较简单的跨域访问模式就是修改chrome的启动参数:
加上:
--disable-web-security
当然可以直接用bat文件写入:
CD C:\Documents and Settings\User\Local Settings\Application Data\Google\Chrome\Application chrome.exe --disable-web-security exit