JavaScript 組件之旅(四):測試 JavaScript 組件

本期,我們要討論的話題是 JavaScript 的測試,以檢查組件的狀態(tài)和工作方式是否符合預期,還會介紹一個可以方便編寫測試用例的測試方法。這里說的測試當然是使用自動化的測試手段,這是軟件質(zhì)量保證(QA)的重要環(huán)節(jié)。就本系列文章介紹的 Smart Queue 來說,我們的測試目標包括:
- Task 對象的創(chuàng)建:第二期的代碼提供了多種創(chuàng)建方式,需要測試對象創(chuàng)建后的狀態(tài)。
- Queue 內(nèi)的任務運行次序:我們提供了兩種改變運行次序的方式:優(yōu)先級和依賴配置,同樣也要測試各種配置對次序的影響。
對于第一個目標,只需檢查對象創(chuàng)建后的屬性是否符合預期即可。我們已經(jīng)多次提到“符合預期”,斷言(Assert)正是為此而設計的。簡單的說,斷言就是確保所測試的表達式結(jié)果為“真”,否則,以某種方式通知測試人員,并幫助其定位斷言失敗的測試用例。
第二個目標稍稍有點復雜。由于我們在組件編碼實現(xiàn)的時候,將排序后的隊列(_sorted)隱藏在了閉包中,所以外部是無法訪問的。有兩種方法可以考慮:(1)重構(gòu)代碼,增加代碼的可測試性,又有兩種重構(gòu)方法:(a)設置 debug 開關(guān),打開時將 _sorted 暴露給外部;(b)增加獨立文件,以構(gòu)建的方式拼接代碼最終生成一個測試版本。(2)測試行為的結(jié)果而不是過程,前一種方法實質(zhì)上是深入到組件的運行時狀態(tài),而這個方法只是檢查組件的運行結(jié)果。本期選用后一種種測試方式,第一種測試方式留給有興趣的讀者練習:)
需要說明的是,我個人不贊成第一種的方法a. 為什么呢?我先說一下這個任務隊列的設計理念:
- 它只是一個隊列,只負責“按需”調(diào)整任務的運行次序,不關(guān)注任務的個體細節(jié)。換句話說,它操作整體的任務,而不關(guān)心任務具體的行為和表現(xiàn)。
- 它是個安全的隊列,使用者(第一期提到的“客戶”)可以放心把任務添加進去,不用擔心這個任務信息會被其他客戶看到。需要說明的是,第二期實現(xiàn)代碼中有
SmartQueue.Queue = [[], [], []], 結(jié)果是外部可以訪問到隊列項。代碼僅供介紹之用,你可以安全地刪除SmartQueue.Queue =來達成安全控制。
回到剛才討論的話題,設置 debug 開關(guān)后,任務信息就潛在的泄漏可能性。進一步地,繼續(xù)改造代碼也可以達成在使用 debug 開關(guān)時的安全性,做法是將開關(guān)的控制放在 SmartQueue 的構(gòu)造函數(shù)中,這樣要求 SmartQueue 實現(xiàn) Singleton 模式(見上一篇文章);一旦創(chuàng)建對象后,不允許修改閉包內(nèi)的 debug 標記。
在編寫具體測試代碼前,我們設計了一個測試方法,以簡化測試代碼(主要是用例)的編寫。簡單地說,就是將測試用例與測試本身的代碼分離——前者以語義良好的方式編寫,后者是一次性編寫,用于處理前者設定的測試用例。用例編寫者需要寫格式形如這樣的代碼:
<ul id="J_test_cases"> <li> <pre>task = new sq.Task({fn: function() { log('unamed') }})</pre> <ul> <li>typeof task.fn === 'function'</li> <li>task.name === 't0'</li> <li>task.level === 1</li> <li>task.dependencies.length === 0</li> <li>task.context == window</li> </ul> </li> <li> <pre>task = new sq.Task({fn: function() { log('unamed') }, name: 'hello'})</pre> <ul> <li>task.name === 'hello'</li> <li>task.level === 1</li> </ul> </li> </ul>
ul li pre (CSS 選擇器路徑,下同)中寫要測試的代碼,相當于前置操作;ul ul li 中對這個代碼進行斷言測試,可以編寫多條斷言。這里建議對基本數(shù)據(jù)類型使用 === 和 !=== 運算符以加強對數(shù)據(jù)類型的預期判斷。
接下來,我們編寫兩個 helper 方法用來輸出和測試:
function log(str) { node.value += str + '\n'; } function assert(expression) { var flag; eval('flag = ' + expression); return typeof(flag) === 'boolean' && flag; }
log 用來向文本框追加信息,assert 用來測試傳入表達式的值。測試方法如下(這里使用了 jQuery):
var sq = SmartQueue, task, total = 0, passed = 0, failed = 0; $('#J_test_cases').children().each(function(index) { eval($('pre', this).text()); task.register(); $('li', this).each(function() { var item = $(this); var flag = assert(item.text()); if(flag) passed ++; else failed ++; item.prepend((flag ? '<font color="green">[PASS]</font>' : '<font color="red">[FAIL]</font>') + ' '); total++; }).wrap('<pre></pre>'); }).end().before('<p>Total: ' + total + ', passed: '+ passed +', failed: ' + failed + '</p>'); sq.fire();
這個結(jié)構(gòu)還可改進一下,比如輸出測試說明而不是具體的代碼,也可以增加后置操作,這里就不再演示了。你還可以查看完整的測試頁面,含有 23 個測試用例和完整的測試實現(xiàn)。
~~~~~~~~~~~~~ 八卦分割線 ~~~~~~~~~~~~~
好吧,我們已經(jīng)體會到了思考和行動的樂趣,走到了系列文章的尾聲,但這只是開始。我們經(jīng)歷了一個很小的實用組件的實現(xiàn)全過程,領(lǐng)略到了 JavaScript 世界的精彩,讓我們繼續(xù)前行~
相關(guān)文章
onkeydown事件解決按回車鍵直接提交數(shù)據(jù)的需求
登陸頁面需要撲捉用戶按下回車自動提交的需求,于是相到在body里添加onkeydown事件跳javascript在提交表單,具體看下實現(xiàn)代碼,希望對你有所幫助2013-04-04
JavaScript如何實現(xiàn)數(shù)組按屬性分組
在JavaScript中,有多種方法可以對數(shù)組按屬性進行分組,這篇文章主要為大家至少介紹了6種常見的方法,感興趣的小伙伴可以跟隨小編一起學習一下2023-08-08
JavaScript 匿名函數(shù)(anonymous function)與閉包(closure)
JavaScript 匿名函數(shù)(anonymous function)與閉包(closure) ,學習js的朋友可以參考下。2011-10-10
JavaScript函數(shù)參數(shù)的傳遞方式詳解
本文主要介紹了JavaScript函數(shù)參數(shù)的傳遞方式,具有很好的參考價值。下面跟著小編一起來看下吧2017-03-03

