JavaScript單元測試ABC

來源:互聯網
上載者:User

前言
  當前,在軟體開發中單元測試越來越受到開發人員的重視,它能提高軟體的開發效率,而且能保障開發的品質。以往,單元測試往往多見於服務端的開發中,但隨著Web編程領域的分工逐漸明細,在前端Javascript開發領域中,也可以進行相關的單元測試,以保障前端開發的品質。
  在伺服器端的單元測試中,都有各種各樣的測試架構,在JavaScript中現在也有一些很優秀的架構,但在本文中,我們將自己動手一步步來實現一個簡單的單元測試架構。
  JS單元測試有很多方面,比較多的是對方法功能檢查,對瀏覽器安全色性檢查,本文主要談第一種。

本文檢查的JS代碼是我以前寫的一個JS日期格式化的方法,原文在這裡(javascript日期格式化函數,跟C#中的使用方法類似),代碼如下: 複製代碼 代碼如下: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;
}

這段代碼目前沒有發現比較嚴重的bug,本文為了測試,我們把 .replace(/MM/g,time.TMonth) 改為 .replace(/MM/g,time.Month),這個錯誤是當月份小於10時,沒有用兩位元表示月份。
  現在有這麼一句話,好的設計都是重構出來的,在本文中也一樣,我們從最簡單的開始。
第一版:用最原始的alert
  作為第一版,我們很偷懶的直接用alert來檢查,完整代碼如下: 複製代碼 代碼如下:<!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>

運行後會彈出 2012 和 4 ,觀察結果我們知道 date.toString("MM")方法是有問題的。
  這種方式很不方便,最大的問題是它只彈出了結果,並沒有給出正確或錯誤的資訊,除非對代碼非常熟悉,否則很難知道彈出的結果是正是誤,下面,我們寫一個斷言(assert)方法來進行測試,明確給出是正是誤的資訊。
第二版:用assert進行檢查
  斷言是表達程式設計人員對於系統應該達到狀態的一種預期,比如有一個方法用於把兩個數字加起來,對於3+2,我們預期這個方法返回的結果是5,如果確實返回5那麼就通過,否則給出錯誤提示。
  斷言是單元測試的核心,在各種單元測試的架構中都提供了斷言功能,這裡我們寫一個簡單的斷言(assert)方法: 複製代碼 代碼如下:function assert(message,result){
if(!result){
throw new Error(message);
}
return true;
}

這個方法接受兩個參數,第一個是錯誤後的提示資訊,第二個是斷言結果
  用斷言測試代碼如下: 複製代碼 代碼如下: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);
}

  運行後會彈出如下視窗:

第三版:進行批量測試

  在第二版中,assert方法可以給出明確的結果,但如果想進行一系列的測試,每個測試都要進行異常捕獲,還是不夠方便。另外,在一般的測試架構中都可以給出成功的個數,失敗的個數,及失敗的錯誤資訊。

  為了可以方便在看到測試結果,這裡我們把結果用有顏色的文字顯示的頁面上,所以這裡要寫一個小的輸出方法PrintMessage: 複製代碼 代碼如下:function PrintMessage(text,color){
var div=document.createElement("div");
div.innerHTML=text;
div.style.color=color;
document.body.appendChild(div);
delete div;
}

  下面,我們就寫一個類似jsTestDriver中的TestCase方法,來進行批量測試: 複製代碼 代碼如下: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");
}

  測試代碼: 複製代碼 代碼如下: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");
}
});

  結果為:

這樣我們一眼就可以看出哪個出錯了。但這樣是否就完美了呢,我們可以看到最後那個測試中 var date=new Date(2012,3,9)是放在testCase外面定義的,並且整個testCase的測試代碼中共用了date,這裡因為各個方法中沒有對date的值進行修改,所以沒出問題,如果某個測試方法中對date的值修改了呢,測試的結果就是不準確的,所以在很多測試架構中都提供了setUp和tearDown方法,用來對統一提供和銷毀測試資料,下面我們就在我們的testCase中加上setUp和tearDown方法。
第四版:統一提供測試資料的批量測試

  首先我們添加setUp和tearDown方法: 複製代碼 代碼如下: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代碼: 複製代碼 代碼如下: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");
}

  運行後的結果跟第三版相同。
小結及參考文章

  上面說了,好的設計是不斷重構的結果,上面的第四版是不是就完美了呢,遠遠沒有達到,這裡只是一個樣本。如果大家需要這方面的知識,我後面可以再寫寫各個測試架構的使用。

  本文只是JS單元測試入門級的樣本,讓初學者對JS的單元測試有個初步概念,屬於拋磚引玉,歡迎各位高人拍磚補充。

  本文參考了《測試驅動的JavaScript開發》(個人覺得還不錯,推薦下)一書第一章,書中的測試案例也是一個時間函數,不過寫的比較複雜,初學者不太容易看懂。
作者:Artwl

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.