最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JavaScript單元測試ABC

 更新時(shí)間:2012年04月12日 12:07:05   作者:  
在服務(wù)器端的單元測試中,都有各種各樣的測試框架,在JavaScript中現(xiàn)在也有一些很優(yōu)秀的框架,但在本文中,我們將自己動(dòng)手一步步來實(shí)現(xiàn)一個(gè)簡單的單元測試框架
前言
  當(dāng)前,在軟件開發(fā)中單元測試越來越受到開發(fā)者的重視,它能提高軟件的開發(fā)效率,而且能保障開發(fā)的質(zhì)量。以往,單元測試往往多見于服務(wù)端的開發(fā)中,但隨著Web編程領(lǐng)域的分工逐漸明細(xì),在前端Javascript開發(fā)領(lǐng)域中,也可以進(jìn)行相關(guān)的單元測試,以保障前端開發(fā)的質(zhì)量。
  在服務(wù)器端的單元測試中,都有各種各樣的測試框架,在JavaScript中現(xiàn)在也有一些很優(yōu)秀的框架,但在本文中,我們將自己動(dòng)手一步步來實(shí)現(xiàn)一個(gè)簡單的單元測試框架。
  JS單元測試有很多方面,比較多的是對(duì)方法功能檢查,對(duì)瀏覽器兼容性檢查,本文主要談第一種。

本文檢查的JS代碼是我以前寫的一個(gè)JS日期格式化的方法,原文在這里(javascript日期格式化函數(shù),跟C#中的使用方法類似),代碼如下:
復(fù)制代碼 代碼如下:

Date.prototype.toString=function(format){
var time={};
time.Year=this.getFullYear();
time.TYear=(""+time.Year).substr(2);
time.Month=this.getMonth()+1;
time.TMonth=time.Month<10?"0"+time.Month:time.Month;
time.Day=this.getDate();
time.TDay=time.Day<10?"0"+time.Day:time.Day;
time.Hour=this.getHours();
time.THour=time.Hour<10?"0"+time.Hour:time.Hour;
time.hour=time.Hour<13?time.Hour:time.Hour-12;
time.Thour=time.hour<10?"0"+time.hour:time.hour;
time.Minute=this.getMinutes();
time.TMinute=time.Minute<10?"0"+time.Minute:time.Minute;
time.Second=this.getSeconds();
time.TSecond=time.Second<10?"0"+time.Second:time.Second;
time.Millisecond=this.getMilliseconds();
var oNumber=time.Millisecond/1000;
if(format!=undefined && format.replace(/\s/g,"").length>0){
format=format
.replace(/yyyy/ig,time.Year)
.replace(/yyy/ig,time.Year)
.replace(/yy/ig,time.TYear)
.replace(/y/ig,time.TYear)
.replace(/MM/g,time.TMonth)
.replace(/M/g,time.Month)
.replace(/dd/ig,time.TDay)
.replace(/d/ig,time.Day)
.replace(/HH/g,time.THour)
.replace(/H/g,time.Hour)
.replace(/hh/g,time.Thour)
.replace(/h/g,time.hour)
.replace(/mm/g,time.TMinute)
.replace(/m/g,time.Minute)
.replace(/ss/ig,time.TSecond)
.replace(/s/ig,time.Second)
.replace(/fff/ig,time.Millisecond)
.replace(/ff/ig,oNumber.toFixed(2)*100)
.replace(/f/ig,oNumber.toFixed(1)*10);
}
else{
format=time.Year+"-"+time.Month+"-"+time.Day+" "+time.Hour+":"+time.Minute+":"+time.Second;
}
return format;
}

這段代碼目前沒有發(fā)現(xiàn)比較嚴(yán)重的bug,本文為了測試,我們把 .replace(/MM/g,time.TMonth) 改為 .replace(/MM/g,time.Month),這個(gè)錯(cuò)誤是當(dāng)月份小于10時(shí),沒有用兩位數(shù)表示月份。
  現(xiàn)在有這么一句話,好的設(shè)計(jì)都是重構(gòu)出來的,在本文中也一樣,我們從最簡單的開始。
第一版:用最原始的alert
  作為第一版,我們很偷懶的直接用alert來檢查,完整代碼如下:
復(fù)制代碼 代碼如下:

<!DOCTYPE html>
<html>
<head>
<title>Demo</title>
<meta charset="utf-8"/>
</head>
<body>
<script type="text/javascript">
Date.prototype.toString=function(format){
var time={};
time.Year=this.getFullYear();
time.TYear=(""+time.Year).substr(2);
time.Month=this.getMonth()+1;
time.TMonth=time.Month<10?"0"+time.Month:time.Month;
time.Day=this.getDate();
time.TDay=time.Day<10?"0"+time.Day:time.Day;
time.Hour=this.getHours();
time.THour=time.Hour<10?"0"+time.Hour:time.Hour;
time.hour=time.Hour<13?time.Hour:time.Hour-12;
time.Thour=time.hour<10?"0"+time.hour:time.hour;
time.Minute=this.getMinutes();
time.TMinute=time.Minute<10?"0"+time.Minute:time.Minute;
time.Second=this.getSeconds();
time.TSecond=time.Second<10?"0"+time.Second:time.Second;
time.Millisecond=this.getMilliseconds();
var oNumber=time.Millisecond/1000;
if(format!=undefined && format.replace(/\s/g,"").length>0){
format=format
.replace(/yyyy/ig,time.Year)
.replace(/yyy/ig,time.Year)
.replace(/yy/ig,time.TYear)
.replace(/y/ig,time.TYear)
.replace(/MM/g,time.Month)
.replace(/M/g,time.Month)
.replace(/dd/ig,time.TDay)
.replace(/d/ig,time.Day)
.replace(/HH/g,time.THour)
.replace(/H/g,time.Hour)
.replace(/hh/g,time.Thour)
.replace(/h/g,time.hour)
.replace(/mm/g,time.TMinute)
.replace(/m/g,time.Minute)
.replace(/ss/ig,time.TSecond)
.replace(/s/ig,time.Second)
.replace(/fff/ig,time.Millisecond)
.replace(/ff/ig,oNumber.toFixed(2)*100)
.replace(/f/ig,oNumber.toFixed(1)*10);
}
else{
format=time.Year+"-"+time.Month+"-"+time.Day+" "+time.Hour+":"+time.Minute+":"+time.Second;
}
return format;
}
var date=new Date(2012,3,9);
alert(date.toString("yyyy"));
alert(date.toString("MM"));
</script>
</body>
</html>

運(yùn)行后會(huì)彈出 2012 和 4 ,觀察結(jié)果我們知道 date.toString("MM")方法是有問題的。
  這種方式很不方便,最大的問題是它只彈出了結(jié)果,并沒有給出正確或錯(cuò)誤的信息,除非對(duì)代碼非常熟悉,否則很難知道彈出的結(jié)果是正是誤,下面,我們寫一個(gè)斷言(assert)方法來進(jìn)行測試,明確給出是正是誤的信息。
第二版:用assert進(jìn)行檢查
  斷言是表達(dá)程序設(shè)計(jì)人員對(duì)于系統(tǒng)應(yīng)該達(dá)到狀態(tài)的一種預(yù)期,比如有一個(gè)方法用于把兩個(gè)數(shù)字加起來,對(duì)于3+2,我們預(yù)期這個(gè)方法返回的結(jié)果是5,如果確實(shí)返回5那么就通過,否則給出錯(cuò)誤提示。
  斷言是單元測試的核心,在各種單元測試的框架中都提供了斷言功能,這里我們寫一個(gè)簡單的斷言(assert)方法:
復(fù)制代碼 代碼如下:

function assert(message,result){
if(!result){
throw new Error(message);
}
return true;
}

這個(gè)方法接受兩個(gè)參數(shù),第一個(gè)是錯(cuò)誤后的提示信息,第二個(gè)是斷言結(jié)果
  用斷言測試代碼如下:
復(fù)制代碼 代碼如下:

var date=new Date(2012,3,9);
try{
assert("yyyy should return full year",date.toString("yyyy")==="2012");
}catch(e){
alert("Test failed:"+e.message);
}
try{
assert("MM should return full month",date.toString("MM")==="04");
}
catch(e){
alert("Test failed:"+e.message);
}

  運(yùn)行后會(huì)彈出如下窗口:

第三版:進(jìn)行批量測試

  在第二版中,assert方法可以給出明確的結(jié)果,但如果想進(jìn)行一系列的測試,每個(gè)測試都要進(jìn)行異常捕獲,還是不夠方便。另外,在一般的測試框架中都可以給出成功的個(gè)數(shù),失敗的個(gè)數(shù),及失敗的錯(cuò)誤信息。

  為了可以方便在看到測試結(jié)果,這里我們把結(jié)果用有顏色的文字顯示的頁面上,所以這里要寫一個(gè)小的輸出方法PrintMessage:
復(fù)制代碼 代碼如下:

function PrintMessage(text,color){
var div=document.createElement("div");
div.innerHTML=text;
div.style.color=color;
document.body.appendChild(div);
delete div;
}

  下面,我們就寫一個(gè)類似jsTestDriver中的TestCase方法,來進(jìn)行批量測試:
復(fù)制代碼 代碼如下:

function testCase(name,tests){
var successCount=0;
var testCount=0;
for(var test in tests){
testCount++;
try{
tests[test]();
PrintMessage(test+" success","#080");
successCount++;
}
catch(e){
PrintMessage(test+" failed:"+e.message,"#800");
}
}
PrintMessage("Test result: "+testCount+" tests,"+successCount+" success, "+ (testCount-successCount)+" failures","#800");
}

  測試代碼:
復(fù)制代碼 代碼如下:

var date=new Date(2012,3,9);
testCase("date toString test",{
yyyy:function(){
assert("yyyy should return 2012",date.toString("yyyy")==="2012");
},
MM:function(){
assert("MM should return 04",date.toString("MM")==="04");
},
dd:function(){
assert("dd should return 09",date.toString("dd")==="09");
}
});

  結(jié)果為:

這樣我們一眼就可以看出哪個(gè)出錯(cuò)了。但這樣是否就完美了呢,我們可以看到最后那個(gè)測試中 var date=new Date(2012,3,9)是放在testCase外面定義的,并且整個(gè)testCase的測試代碼中共用了date,這里因?yàn)楦鱾€(gè)方法中沒有對(duì)date的值進(jìn)行修改,所以沒出問題,如果某個(gè)測試方法中對(duì)date的值修改了呢,測試的結(jié)果就是不準(zhǔn)確的,所以在很多測試框架中都提供了setUp和tearDown方法,用來對(duì)統(tǒng)一提供和銷毀測試數(shù)據(jù),下面我們就在我們的testCase中加上setUp和tearDown方法。
第四版:統(tǒng)一提供測試數(shù)據(jù)的批量測試

  首先我們添加setUp和tearDown方法:
復(fù)制代碼 代碼如下:

testCase("date toString",{
setUp:function(){
this.date=new Date(2012,3,9);
},
tearDown:function(){
delete this.date;
},
yyyy:function(){
assert("yyyy should return 2012",this.date.toString("yyyy")==="2012");
},
MM:function(){
assert("MM should return 04",this.date.toString("MM")==="04");
},
dd:function(){
assert("dd should return 09",this.date.toString("dd")==="09");
}
});

  由于setUp和tearDown方法不參與測試,所以我們要修改testCase代碼:
復(fù)制代碼 代碼如下:

function testCase(name,tests){
var successCount=0;
var testCount=0;
var hasSetUp=typeof tests.setUp == "function";
var hasTearDown=typeof tests.tearDown == "function";
for(var test in tests){
if(test==="setUp"||test==="tearDown"){
continue;
}
testCount++;
try{
if(hasSetUp){
tests.setUp();
}
tests[test]();
PrintMessage(test+" success","#080");

if(hasTearDown){
tests.tearDown();
}

successCount++;
}
catch(e){
PrintMessage(test+" failed:"+e.message,"#800");
}
}
PrintMessage("Test result: "+testCount+" tests,"+successCount+" success, "+ (testCount-successCount)+" failures","#800");
}

  運(yùn)行后的結(jié)果跟第三版相同。
小結(jié)及參考文章

  上面說了,好的設(shè)計(jì)是不斷重構(gòu)的結(jié)果,上面的第四版是不是就完美了呢,遠(yuǎn)遠(yuǎn)沒有達(dá)到,這里只是一個(gè)示例。如果大家需要這方面的知識(shí),我后面可以再寫寫各個(gè)測試框架的使用。

  本文只是JS單元測試入門級(jí)的示例,讓初學(xué)者對(duì)JS的單元測試有個(gè)初步概念,屬于拋磚引玉,歡迎各位高人拍磚補(bǔ)充。

  本文參考了《測試驅(qū)動(dòng)的JavaScript開發(fā)》(個(gè)人覺得還不錯(cuò),推薦下)一書第一章,書中的測試用例也是一個(gè)時(shí)間函數(shù),不過寫的比較復(fù)雜,初學(xué)者不太容易看懂。
作者:Artwl

相關(guān)文章

最新評(píng)論

绵阳市| 亚东县| 敖汉旗| 远安县| 育儿| 阿拉善右旗| 重庆市| 阳城县| 安图县| 阿巴嘎旗| 桃园县| 平泉县| 红原县| 黎平县| 富蕴县| 灵寿县| 绥滨县| 公主岭市| 旅游| 荣昌县| 南召县| 宜兴市| 汕尾市| 于都县| 鸡西市| 衡山县| 长春市| 南充市| 余江县| 汝阳县| 灵武市| 加查县| 玉山县| 呈贡县| 勃利县| 巧家县| 福泉市| 吴旗县| 杨浦区| 高青县| 天台县|