QWrap的js单元测试工具

QWrap的js/_tools下面,有几个工具,还是挺实用的。
今天介绍一下单元测试工具。
QWrap的单元测试(unittest)工具,是基于jsspec(http://jania.pe.kr/aw/moin.cgi/JSSpec)的语法来的。其css也是完全照搬jsspec。
相对于jsspec,改变有:代码重构、弃用多次运行(仅对未通过的试例)策略、移去对原型的渲染、部分功能增删。

如何使用qwrap的jsspec:
<link rel="stylesheet" type="text/css" href="http://dev.qwrap.com/resource/js/_tools/unittest/JSSpec.css" />
<script type="text/javascript" src="http://dev.qwrap.com/resource/js/_tools/unittest/UnitTest.js"></script>
<script type="text/javascript" src="...自己的代码文件"></script>
<script>/*单元测试代码*/</script>
例如:
View Code
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>UnitTest</title>
<meta http-equiv="Content-Type" content="text/html; charset=GB2312" />
<link rel="stylesheet" type="text/css" href="http://dev.qwrap.com/resource/js/_tools/unittest/JSSpec.css" />
<script type="text/javascript" src="http://dev.qwrap.com/resource/js/_tools/unittest/UnitTest.js"></script>
<script type="text/javascript" >
//自己的代码
//
String相关方法:
function trim(s){
return s.replace(/^\s+|\s+$/g,'');
}
function camelize(s){
return s.replace(/-(\w)/g, function(a,b){return b.toUpperCase();});
}

//Number相关
function add50(n){
return n+50;
}
function mul5(n){
return n*5;
};
</script>

<script type="text/javascript">// <![CDATA[
//
测试用例代码

describe(
'String相关', {
'trim': function() {
value_of(trim(
"foo")).should_be('foo');
value_of(trim(
" foo")).should_be('foo');
value_of(trim(
" foo ")).should_be('foo');
},
'camelize': function() {
value_of(camelize(
"abc")).should_be('abc');
value_of(camelize(
"abc-def")).should_be('abcDef');
value_of(camelize(
"abc-def-hij")).should_be('abcDefHij');
}
});

describe(
'Number相关', {
'add50': function() {
value_of(add50(
1)).should_be(51);
value_of(add50(
3)).should_be(53);
value_of(add50(
-5)).should_be(45);
},
'mul5': function() {
value_of(mul5(
0)).should_be(0);
value_of(mul5(
3)).should_be(15);
value_of(mul5(
-3)).should_be(-15);
}
});
// ]]></script>
</head>
<body>
</body>
</html>

点击运行以上代码。http://dev.qwrap.com/resource/js/_tools/unittest/_examples/UnitTest_4cnblogs.html
那么我们具体看一下如何写单元测试代码。
这是一段典型的代码:
View Code
describe('String相关', {
'trim': function() {
value_of(trim(
"foo")).should_be('foo');
value_of(trim(
" foo")).should_be('foo');
value_of(trim(
" foo ")).should_be('foo');
},
'camelize': function() {
value_of(camelize(
"abc")).should_be('abc');
value_of(camelize(
"abc-def")).should_be('abcDef');
value_of(camelize(
"abc-def-hij")).should_be('abcDefHij');
}
});

UnitTest.js会在window下产生三个变量:
    UnitTest命名空间。
    describe函数。用来定义一个“测试用例组”。格式为:“测试用例名”与“测试用例函数”的键值对json对象。
    value_of函数。用来产生一个主语。
页面会在onload后按顺序执行测试用例,并在页面产生测试报告。报告是即时演染的。
测试用例有四种状态:未运行/运行中/未通过/已通过,分别以白色/黄色/绿色/红色象征。
每个测试用例函数里可以有多个断言。如果所有的断言都正确的话,则这个测试用例通过检查。


断言的形式通常为:
//var a=3-2;
value_of(a).should_be(1);//主(o).谓(宾);
详解:
value_of(a)//以a为中心,创建一个主语。
.should_be//谓语
(1);//1是宾语。

“主(o).谓(宾);”断言中,常用的谓语有:
should_be(value)、should_not_be(value)、should_have_method(methodName)、should_have_property(propertyName);
扩展的写法还有: should(op,value);
其中,op为比较符。如:
value_of(a).should('===',1);
value_of(a).should('!==',3);
value_of(a).should('<',3);
value_of(a).should('>',0);

还有一些其它用法,这里就不一一详举了。
很多谓语都是由这个万能谓语演化出来的:
_should: function(property,value,op,selfPre,selfTail,valuePre,valueTail,isReverse)。
目前unittest只提供以下谓语,如果觉得不够用的话,可以自己添加。
实现参考unittest.js中的这部分代码:
View Code
mix(Subject.prototype,{
_should:
function(property,value,op,selfPre,selfTail,valuePre,valueTail,isReverse){
var selfDesc=[
selfPre,
property
==null?"self":"self[property]",
selfTail
],
valueDesc
=[
valuePre,
"value",
valueTail
];
var desc=[].concat( isReverse?valueDesc:selfDesc, op, isReverse?selfDesc:valueDesc );
var sFun=desc.join(" ");
//alert([sFun,this.self,property,value]);
var tempCur={
self:
this.self,
property:property,
value:value,
sFun:sFun
};
try{
var fun=new Function("self","property","value","return ("+sFun+");");
}
catch(ex){//错误的调用了_should方法,造成matcher不合法
currentCaseStatus|=2;
currentErrorMsg
="Matcher is illegle: "+ex.message;
currentShouldInfo
=tempCur;
return;
}
try{
var result=fun(this.self,property,value);
}
catch(ex){//运行matcher时抛错
currentCaseStatus|=4;
currentErrorMsg
="Not match: "+ex.message;
currentShouldInfo
=tempCur;
return;
}
if(result !== true) {//Not Match
currentCaseStatus|=4;
currentErrorMsg
="Not match";
currentShouldInfo
=tempCur;
return;
}
currentCaseStatus
|=1;//Match
return result;
},
should:
function (op,value){
return this._should(null,value,op);
},
should_be:
function (value){
return this._should(null,value,"===");
},
should_not_be:
function (value){
return this._should(null,value,"!==");
},
should_have_method:
function(methodName){
return this._should(methodName,"function","==","typeof");
},
should_have_property:
function(property){
return this._should(null,property,"in",null,null,null,null,true);
},
should_contains:
function(value){
return this._should(null,value,".contains",null,null,"(",")");
},
property_should:
function (property,op,value){
return this._should(property,value,op);
},
property_should_be:
function (property,value){
return this._should(property,value,"===");
},
property_should_not_be:
function (property,value){
return this._should(property,value,"!==");
},

log:
function(message){
Logger.log(
this.self,message);
}
});


一个小问题:
由于qwrap的单元测试每个用户只跑一次,导致有时候在某些浏览器下,错误行号不是我们想要的。
所以在实际的应用中,可能会看到有的断言后面多了一个.line的尾巴:
value_of(a).should('<',3).line
它是解决行号不准的一个方案。不过某些编码规范检查工具会拒掉这种写法。可以改成“.toString()”来通过检查。

另:其实这里的主语Subject:
UnitTest.Subject=function (self){
    this.self=self;
};
window.value_of=function (self){
    return new UnitTest.Subject(self);
};

它也是一个QWrap里所说到的wrap(为了保护核对象(core)的纯净,在外面包一层皮(wrap))。不过,它的核的名字,不是叫qwrap所推荐的"core",而是叫"self"。


单元测试在QWrap的开发中,已经有很成熟的应用,如:
qwrap的core的单元测试:http://dev.qwrap.com/resource/js/core/_tests/UnitTest_Core.html
qwrap的core_dom_retouch单元测试:http://dev.qwrap.com/resource/js/dom/_tests/UnitTest_Dom.html
我们在修改代码后,都会运行一下单元测试,以确保以前的功能没有被改坏。


posted on 2011-03-07 16:16  JKisJK  阅读(1919)  评论(0编辑  收藏  举报

导航