QUnit是一套由jQuery團隊開發的,非常強大的用於對JavaScript進行單元測試的架構。本文將介紹什麼是QUnit,以及為何要關心代碼測試。
什麼是QUnit
Qunit是一款強大的用於協助調試代碼的,JavaScript單元測試架構。QUnit由jQuery團隊成員編寫,是jQuery的官方測試套件,不僅如此,QUnit還可以測試任何常規JavaScript代碼,甚至可以通過一些像Rhino或者V8這樣的JavaScript引擎,測試服務端JavaScript代碼。
如果不熟悉“單元測試”的概念,不要擔心。並不難理解:
"在電腦編程中,單元測試(又稱為模組測試)是針對程式模組(軟體設計的最小單位)來進行正確性檢驗的測試工作。程式單元是應用的最小可測試組件。在過程化編程中,一個單元就是單個程式、函數、過程等;對於物件導向編程,最小單元就是方法,包括基類(超類)、抽象類別、或者衍生類別(子類)中的方法。"——引自維基百科。
簡單來說,你為代碼的每一個功能編寫測試案例,如果所有的測試都通過了,就可以確保代碼沒有bug了(通常,還是由測試有多徹底而定)。
為什麼要測試代碼
如果你以前從未寫過任何單元測試,你可能直接將你的代碼應用到網站上,點擊一會看看是否有什麼問題出現,並且嘗試去解決你所發現的問題,採用這種方法會有很多的問題。
首先,這是非常乏味的。點擊其實並不是一項簡單的工作,因為需要保證每一個東西都被點擊到而且極有可能漏掉一兩個。其次,為測試做的每一件事情都是不可重用的,這就意味著它很難迴歸。什麼是迴歸?想像一下,你寫了一些代碼並測試他們,修複了所有你發現的缺陷,然後發布。這時,一個使用者發過來一些關於新的bug的反饋,並且有一些新的需求。你又回到代碼中,處理這些新的bug,並增加新的功能。接下來可能會發生的就是一些舊的缺陷又重現了,這就叫“迴歸”。這樣,你就不得不重新點擊一遍,而且有可能你還找不到這些舊的缺陷;即使你這麼做,這還需要一段時間才能弄清楚你的問題是由迴歸引起的。使用單元測試,你寫測試案例去發現缺陷,一旦代碼被修改,您通過測試再篩選一次。一旦出現迴歸,一些測試案例一定會失敗,你可以很容易地認出他們,知道哪部分程式碼封裝含了錯誤。既然你知道你剛才修改了什麼,就可以很容易地解決問題。
單元測試的另外一個尤其是對於Web開發的優點:讓跨瀏覽器安全色性測試變得更容易。僅僅在不同瀏覽器中運行你的測試案例,一旦某個瀏覽器出現問題,修複它並重新運行這些測試案例,確保不會在別的瀏覽器引起迴歸,一旦全部通過測試,就可以肯定的說,所有的目標瀏覽器都支援。
我喜歡提及一個John Resig的項目:TestSwarm。TestSwarm通過分發,將JavaScript單元測試帶到了一個新的層次。這是一個包含很多測試案例的網站,任何人都可以去那運行一些測試案例,然後返回結果會返回到伺服器。通過這種方式,代碼會非常迅速的在不同的瀏覽器進行測試,甚至不同的平台運行。
怎麼用QUnit編寫測試案例
如何正確地用QUnit寫單元測試呢?首先,您需要搭建一個測試環境:
<!DOCTYPE html><br /><html><br /><head><br /> <title>QUnit Test Suite</title><br /> <link rel="stylesheet" href="http://github.com/jquery/qunit/raw/master/qunit/qunit.css" mce_href="http://github.com/jquery/qunit/raw/master/qunit/qunit.css" type="text/css" media="screen"><br /> <mce:script type="text/javascript" src="http://github.com/jquery/qunit/raw/master/qunit/qunit.js" mce_src="http://github.com/jquery/qunit/raw/master/qunit/qunit.js"></mce:script><br /> <!-- Your project file goes here --><br /> <mce:script type="text/javascript" src="myProject.js" mce_src="myProject.js"></mce:script><br /> <!-- Your tests file goes here --><br /> <mce:script type="text/javascript" src="myTests.js" mce_src="myTests.js"></mce:script><br /></head><br /><body><br /> <h1 id="qunit-header">QUnit Test Suite</h1><br /> <h2 id="qunit-banner"></h2><br /> <div id="qunit-testrunner-toolbar"></div><br /> <h2 id="qunit-userAgent"></h2><br /> <ol id="qunit-tests"></ol><br /></body><br /></html>
正如你所見,在這裡使用了一個被託管的QUnit架構版本。
將要被測試的代碼需要添加到myProject.js中,並且你的測試案例應該插入到myTest.js。要運行這些測試,只需在一個瀏覽器中開啟這個html檔案。現在需要寫一些測試案例了。
單元測試的基石是斷言。
“斷言是一個命題,預測你的代碼的返回結果。如果預測是假的,宣告失敗,你就知道出了問題。”
運行斷言,需要把它們放入測試案例中:
// Let's test this function<br />function isEven(val) {<br /> return val % 2 === 0;<br />}<br />test('isEven()', function() {<br /> ok(isEven(0), 'Zero is an even number');<br /> ok(isEven(2), 'So is two');<br /> ok(isEven(-4), 'So is negative four');<br /> ok(!isEven(1), 'One is not an even number');<br /> ok(!isEven(-7), 'Neither is negative seven');<br />})
這裡,我們定義一個函數:isEven,用來檢測一個數字是否為奇數,並且我們希望測試這個函數來確認它不會返回錯誤答案。
我們首先調用test(),它構建了一個測試案例;第一個參數是一個將被顯示在結果中的字串,第二個參數是包括我們斷主的一個回呼函數。
我們寫了5個斷言,所有的都是布爾型的。一個布爾型的斷言,期望它的第一個參數為true。第二個參數依然是要顯示在結果中的訊息。
下面是你想要得到的,只要你運行測試案例:
由於所有的斷言都已成功通過,我們可以高興的認為isEven()工作正常。
讓我們看看如果一個宣告失敗了會發生什麼。// Let's test this function<br />function isEven(val) {<br /> return val % 2 === 0;<br />}<br />test('isEven()', function() {<br /> ok(isEven(0), 'Zero is an even number');<br /> ok(isEven(2), 'So is two');<br /> ok(isEven(-4), 'So is negative four');<br /> ok(!isEven(1), 'One is not an even number');<br /> ok(!isEven(-7), 'Neither does negative seven');<br /> // Fails<br /> ok(isEven(3), 'Three is an even number');<br />})下面是結果:該宣告失敗因為我們故意把它寫錯,但是在你的項目中,如果測試未通過,並且所有的斷言都是正確的,你將發現一個bug。
更多斷言ok()不僅是QUnit提供的唯一斷言, 當在測試你的項目時,還會有一些非常有用的其他類型的斷言:
比較斷言
比較斷言,equals(),期望它的第一個參數(是實際值)等於它的第二個參數(期望值)。它很類似於ok(),但均會輸入實現和期望值,使得高度更加簡單,像ok()一樣,它可帶一個可選的第三個參數作為顯示的訊息。
所以可以代替:
test('assertions', function() {<br /> ok( 1 == 1, 'one equals one');<br />})
你可以這樣寫:test('assertions', function() {<br /> equals( 1, 1, 'one equals one');<br />})
注意最後一個“1”,這是比較值
如果兩個值不相等:
test('assertions', function() {<br /> equals( 2, 1, 'one equals one');<br />})
提供更多些資訊,讓生活更簡單些。
比較斷言使用“==”來比較它的參數,所以它不能處理數組或對象的比較:
test('test', function() {<br /> equals( {}, {}, 'fails, these are different objects');<br /> equals( {a: 1}, {a: 1} , 'fails');<br /> equals( [], [], 'fails, there are different arrays');<br /> equals( [1], [1], 'fails');<br />})為了測試這種相等,QUnit提供了另外一種斷言:
恒等斷言
。
恒等斷言
恒等斷言,same(),期望相同的參數相等,但是它較深的採用遞迴比較斷言,不僅作用於原始類型,而且包括數組和對象。斷言,在前面的例子中,如果你把他們改成恒等斷言將全部通過。test('test', function() {<br /> same( {}, {}, 'passes, objects have the same content');<br /> same( {a: 1}, {a: 1} , 'passes');<br /> same( [], [], 'passes, arrays have the same content');<br /> same( [1], [1], 'passes');<br />})注意same()使用”===”去比較,如有必要的話,所以它在比較特殊值的時候就派上用場了。test('test', function() {<br /> equals( 0, false, 'true');<br /> same( 0, false, 'false');<br /> equals( null, undefined, 'true');<br /> same( null, undefined, 'false');<br />})
結構化你的斷言
把所有的斷言放在一個單獨的測試案例中是相當不好的想法,因為這很難去維護,並且不能返回一個純淨的結果。你需要做的就是結構化他們,把他們放在不同的測試案例,每個目標為一個單獨功能。
甚至可以通過調用模組函數來把測試案例組織到不同的模組:
module('Module A');<br />test('a test', function() {});<br />test('an another test', function() {});<br />module('Module B');<br />test('a test', function() {});<br />test('an another test', function() {});
非同步測試
在前面的樣本中,所有的斷言都是同步調用的,這意味著他們是一個接著一個啟動並執行。在這個真實的世界,同樣 存在著很多非同步函數,例如Ajax請求或通過setTimeout()或sestInterval()調用的方法。我們如何去測試這些種類的方法呢?QUnit提供了一個特殊的叫做和“非同步測試”的測試案例,提供給非同步測試:
讓我們首先嘗試用常規的方法寫:test('asynchronous test', function() {<br /> setTimeout(function() {<br /> ok(true);<br /> }, 100)<br />})
看到了?這就好像我們沒有寫任何斷言一樣。這是因為斷言是被非同步執行的,到它被調用的時候,此次測試已經執行完成。
這是正確的版本:
test('asynchronous test', function() {<br /> // Pause the test first<br /> stop();</p><p> setTimeout(function() {<br /> ok(true);</p><p> // After the assertion has been called,<br /> // continue the test<br /> start();<br /> }, 100)<br />})
在這,我們使用了stop()去暫停此次測試案例, 並且在斷言被調用以後,我們使用start()繼續。
在調用完test()後立即調用stop()是很平常的;所以QUnit提供了一個捷徑:asyncTest()。你可以像這樣重寫之前的樣本:
asyncTest('asynchronous test', function() {<br /> // The test is automatically paused </p><p> setTimeout(function() {<br /> ok(true); </p><p> // After the assertion has been called,<br /> // continue the test<br /> start();<br /> }, 100)<br />}) 還有一點要注意:setTimeout()通常會調用它自己的回呼函數,但如果它是一個自訂的函數(例如:一個Ajax調用)。你如何確認回呼函數被調用了呢?並且如果回呼函數沒有被調用,start()將不會被執行,整個單元測試將被掛起:所以這就是你需要做的:// A custom function<br />function ajax(successCallback) {<br /> $.ajax({<br /> url: 'server.php',<br /> success: successCallback<br /> });<br />} </p><p>test('asynchronous test', function() {<br /> // Pause the test, and fail it if start() isn't called after one second<br /> stop(1000); </p><p> ajax(function() {<br /> // ...asynchronous assertions </p><p> start();<br /> })<br />})
你可以通過延時去stop(),它告知QUnit,“如果start()在延時後沒有被調用,你應未通過測試”。你可以確認的是整個測試沒有掛起而且如果哪裡出了問題你可以注意到。
那麼多個非同步函數呢?你在哪裡放置start()?可把它放在setTimeout()裡:
// A custom function<br />function ajax(successCallback) {<br /> $.ajax({<br /> url: 'server.php',<br /> success: successCallback<br /> });<br />} </p><p>test('asynchronous test', function() {<br /> // Pause the test<br /> stop(); </p><p> ajax(function() {<br /> // ...asynchronous assertions<br /> }) </p><p> ajax(function() {<br /> // ...asynchronous assertions<br /> }) </p><p> setTimeout(function() {<br /> start();<br /> }, 2000);<br />}) 延時應該適當的長足夠來允許二者的回呼函數在測試繼續執行前被調用。但是如果其中一個回呼函數沒有被調用怎麼辦?你怎樣去知道?這就是expect()加入的原因:// A custom function<br />function ajax(successCallback) {<br /> $.ajax({<br /> url: 'server.php',<br /> success: successCallback<br /> });<br />} </p><p>test('asynchronous test', function() {<br /> // Pause the test<br /> stop(); </p><p> // Tell QUnit that you expect three assertions to run<br /> expect(3); </p><p> ajax(function() {<br /> ok(true);<br /> }) </p><p> ajax(function() {<br /> ok(true);<br /> ok(true);<br /> }) </p><p> setTimeout(function() {<br /> start();<br /> }, 2000);<br />})
你給expect()傳一個數字告知QUnit你期望X個斷言去執行,如果一個斷言未被執行,這個數字將不會匹配,而且你瘵會注意到有些東西出錯了。
這仍有一個expect()的捷徑:你只需給test()或asyncTest()的第二個參數傳遞一個數字:
// A custom function<br />function ajax(successCallback) {<br /> $.ajax({<br /> url: 'server.php',<br /> success: successCallback<br /> });<br />} </p><p>// Tell QUnit that you expect three assertion to run<br />test('asynchronous test', 3, function() {<br /> // Pause the test<br /> stop(); </p><p> ajax(function() {<br /> ok(true);<br /> }) </p><p> ajax(function() {<br /> ok(true);<br /> ok(true);<br /> }) </p><p> setTimeout(function() {<br /> start();<br /> }, 2000);<br />})
總結
這就是開始使用QUnit所需要瞭解的全部內容。單元測試是一個在發布代碼前進行測試的非常好的方法。如果以前沒有寫過任何的單元測試,現在是時候開始了!多謝閱讀!
原文出處:http://net.tutsplus.com/tutorials/javascript-ajax/how-to-test-your-javascript-code-with-qunit/