前些天在项目中由于要用到单元测试,包装了一个基于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