自動化測試軟體對於開發來說是一個很重要的工具,而單元測試對於自動化測試來說是基本組成部分:軟體的每一個組件或者單元可以在非人工介入的情況下,使用測試載入器一遍遍的重複執行。換句話說,就是你可以寫一次測試,然後不用付出額外成本的任意執行多次。
除了測試覆蓋率帶來的好處外,測試還可以指導軟體設計,這就是TDD(基於測試驅動的設計):先有測試,後有開發代碼。你開始寫一個簡單的測試,然後寫實現代碼並保證代碼能通過測試。完成上述步驟後,擴充你的測試,讓他覆蓋更多設計功能,然後再編寫實現代碼。重複上面的步驟直到完成開發,你會發現你的實現代碼和之前的版本已經非常不一樣了。
JavaScript的單元測試和其他語言沒什麼不同,你需要一個提供測試回合器的小架構,他同時提供寫測試案例的工具。
自動化單元測試 問題
你想自動化測試你的應用和架構,甚至想使用TDD的開發方式。你或許會想些一個自己的測試架構,但是那需要很多額外的工作,涉及到太多的細節,還需要處理在不同瀏覽器中測試JavaScript的問題。
解決方案
現在已經有很多JavaScript的單元測試架構了,例如你可以選擇QUnit。QUnit是jquery使用的單元測試架構,而且他已經被廣泛的使用在了不同的項目中。使用QUnit很簡單,你只需要添加兩個相關檔案到你的html頁面即可。QUnit包括qunit.js:測試回合器和測試架構,qunit.css:測試頁面用於顯示測試結果的css檔案。
<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>QUnit basic example</title> <link rel="stylesheet" href="/resources/qunit.css"></head><body> <div id="qunit"></div> <div id="qunit-fixture"></div> <script src="/resources/qunit.js"></script> <script> test( "a basic test example", function() { var value = "hello"; equal( value, "hello", "We expect value to be hello" ); }); </script></body></html>
在瀏覽器中開啟上面的檔案,顯示結果如下:
唯一需要的標籤是<body>中含有id="qunit-fixture"的<div>,他對於所有QUnit的測試都是必須的,即使這個div元素是空的,他為測試提供夾具(fixture),我們會在“保持測試原子性”中詳細介紹。
有趣的部分是跟在測試回合器(qunit.js)後面的指令碼標籤,他包含一個test方法。他包含兩個參數,第一個參數是字串類型,表示測試名稱,他將會顯示在測試結果和方法上。第二個參數是一個函數,包含實際的測試代碼。他包含一個或者多個斷言,上面的例子包含兩個斷言:ok() 和 equal()。我們會在“斷言結果”中詳細介紹。
我們注意到這裡沒有使用到document-ready,這次因為測試回合器會把test()添加到測試隊列中,測試案例會被順延強制。
討論
測試套件的頁首顯示頁面名稱,所有測試通過的時候,顯示綠條;當至少有一條測試失敗的時候顯示紅條。有選擇框可供過濾結果,此外還有一個藍條用來顯示瀏覽器資訊。選擇框中有"Hide passed tests",當測試很多的時候可以使用它隱藏成功的測試,只顯示失敗的測試。
選擇“noglobals”,會讓QUnit在每次測試的開始和結束的時候羅列window的所有屬性,並比較不同點。如果存在屬性的添加和刪除操作,測試失敗,並顯示不同點資訊。這樣可以驗證我們的測試代碼和被測試代碼沒有暴露任何的全域屬性。
“notrycatch”選擇框的作用是,告訴QUnit不使用try-catch跑測試,當有異常拋出的時候,測試回合器會停止運行。但是你會獲得一個內部異常,這樣在我們使用老瀏覽器(例如ie6)做測試的時候會有協助。
頁首之下是測試總結,顯示測試總用時,成功和失敗的測試總數。當測試還在啟動並執行時候,他會顯示哪個測試案例正在被執行。
頁面的主體部分是測試結果,每個實體以名字開頭,後面跟著失敗數、成功數和總斷言數。點擊實體將會顯示每一個斷言,經常會顯示期望值和實際值。最後的“Rerun”連結會單獨運行測試實體。
斷言結果 問題
任何單元測試的實際元素都是斷言,測試的開發人員需要使用測試架構,將期望值和運行測試獲得的實際值進行比較。
解決方案
QUnit提供三種斷言。
ok( truthy [, message ] )
ok()是最基本的方法,他只需要一個參數,如果參數等於true,斷言成功,否則失敗。例外他還接受額外的字串參數,用於顯示測試結果。
test( "ok test", function() { ok( true, "true succeeds" ); ok( "non-empty", "non-empty string succeeds" ); ok( false, "false fails" ); ok( 0, "0 fails" ); ok( NaN, "NaN fails" ); ok( "", "empty string fails" ); ok( null, "null fails" ); ok( undefined, "undefined fails" );});
equal( actual, expected [, message ] )
equal()方法使用簡單的比較符(==)來比較期望值和實際值。當他們相等的時候,斷言成功,否則失敗。當失敗的時候,期望值和實際值都會顯示,另外還顯示訊息。
test( "equal test", function() { equal( 0, 0, "Zero; equal succeeds" ); equal( "", 0, "Empty, Zero; equal succeeds" ); equal( "", "", "Empty, Empty; equal succeeds" ); equal( 0, 0, "Zero, Zero; equal succeeds" ); equal( "three", 3, "Three, 3; equal fails" ); equal( null, false, "null, false; equal fails" );});
使用ok() 和 equal()讓我們更容易的找到失敗的測試,因為測試失敗的時候他會很明顯的告訴我們哪個值導致了問題。當你需要使用嚴格比較(===)的時候,可以使用strictEqual()。
deepEqual( actual, expected [, message ] )
deepEqual()可以像equal()那樣使用,但是他適用的情境更多。他不是使用簡單比較符(==),他使用的是更精確的比較符(===)。這種情況下,undefined不等於null,0或者Null 字元串(“”)。他同時也比較對象的內容,{key: value} 等於 {key: value},甚至比較的兩個對象有不同的執行個體。deepEqual()同樣也處理NaN,dates,Regex,數組和函數,而equal()只檢查對象執行個體。
test( "deepEqual test", function() { var obj = { foo: "bar" }; deepEqual( obj, { foo: "bar" }, "Two objects can be the same in value" );});
如果你不想明確的比較兩個對象的內容仍然可以使用equal(),但是deepEqual()是更好的選擇。
同步回調 問題
有時候你的代碼可能會阻止回調斷言的執行,導致測試無聲無息的就失敗了。
解決方案
QUnit提供了一個特殊的斷言,定義了測試包含的總斷言數。當測試結束的時候,斷言總數不相等,無論其他斷言的執行情況,都會返回失敗。使用上也相當簡單,在測試開始的時候調用expect(),只需要傳遞期望的斷言數作為方法參數。
test( "a test", function() { expect( 2 ); function calc( x, operation ) { return operation( x ); } var result = calc( 2, function( x ) { ok( true, "calc() calls operation function" ); return x * x; }); equal( result, 4, "2 square equals 4" );});
另外一種方式是,把期望斷言數作為他的第二個參數傳給test():
test( "a test", 2, function() { function calc( x, operation ) { return operation( x ); } var result = calc( 2, function( x ) { ok( true, "calc() calls operation function" ); return x * x; }); equal( result, 4, "2 square equals 4" );});
執行個體:
test( "a test", 1, function() { var $body = $( "body" ); $body.on( "click", function() { ok( true, "body was clicked!" ); }); $body.trigger( "click" );});
非同步回調 問題
雖然expect()對於同步回調的測試是有協助的,但他不能用來處理非同步回調的測試,非同步回調和測試回合器中的執行隊列的執行相衝突。在測試代碼中執行一個timeout、interval或者ajax請求的時候,測試回合器只是會繼續執行測試案例剩餘的代碼,然後接著執行測試隊列中剩餘的用例,而不會去執行非同步作業。
解決方案
我們不使用test(),取而代之將使用asyncTest(),當你的測試代碼執行完畢準備繼續的時候執行start()。
asyncTest( "asynchronous test: one second later!", function() { expect( 1 ); setTimeout(function() { ok( true, "Passed and ready to resume!" ); start(); }, 1000);});
執行個體:
asyncTest( "asynchronous test: video ready to play", 1, function() { var $video = $( "video" ); $video.on( "canplaythrough", function() { ok( true, "video has loaded and is ready to play" ); start(); });});
文章來源:http://qunitjs.com/cookbook/