文章目錄
- 題外話
- TDD的背景
- TDD in HTML & JavaScript 概述
- TDD in HTML & JavaScript 之可行性
- TDD in JavaScript 之最佳實務
題外話
昨天就想發起這個話題的討論,只是覺得對於討論的支援,部落格園現有的功能天然似乎還不能很好的支援。所以有了突然發現想在部落格園發起一個有價值的討論其實很難一文。亞曆山大同志提到“部落格園的討論需要發起爭議性話題,比如 .net sucks之類”。回顧如關於近期C#大論戰的回應這樣的近期引起討論的焦點話題,貌似確實如此。深以為歎。近期的C#大論戰是幸運的,儘管中間還是參雜了很多口水,李建忠老師的加入,一定程度上最終將話題引向了正確的方向。幸哉。我這個圍觀群眾也從中獲益良多。不僅僅是對於這個技術話題正確理解,還包括李老師在他的部落格最後提到的:拋掉“非要辯個勝負,分個高低”的怪誕氛圍,而是來一些紮紮實實的技術說理過程,相信會更有意義——如是,則國內技術社區成長可待。
言歸正傳,還是儘快開始我想討論的主題,且不管討論最終成效如何,既然發起討論,還是先儘可能分享自己的想法,以示誠意。
TDD的背景
自從03年Beck正式提出(事實上在00年,Beck提出eXtreme Programming時,就已經提出了這個詞)Test-driven design/development這樣一個基於測試優先、重構和迭代的革命性的開發方法以來,無數的實踐已經證明,對於適合進行TDD的領域,TDD能夠極大地提高代碼的可維護性和開發效率。
TDD的基本流程圖如下:
在這樣一個迭代的流程中,在寫任何的production code之前,先寫test,再寫production code,並且不斷地對代碼進行清理和重構,並且每次迭代都要進行迴歸測試,保證新增的test和production code不會break任何已有的test和production代碼。
一般來講,支援自動化的迴歸測試的工具相對比較容易實現。整個流程中的痛點在於:當先行寫test代碼的時候,必然要求先定義被測試的production code的外部介面,對於第一次迭代,自然沒有問題;但是,由於需求的變更,或者整體設計的變更,在後續的迭代過程中,經常會發生,已有的已經實現並且包含完整測試的production code的外部介面需要變更或者說重構;儘管從理論上,絕大多數的重構需求,都有規律甚至是模式可循,但是,如果完全依賴於人工操作,則不僅效率不高,且極易出錯。所以,但凡成功的TDD實踐,其中都不乏很多支援重構的工具。比如,現行絕大多數的整合式開發環境,都有很多自動化的代碼重構工具,大大的降低了代碼重構的成本。
但是還有一些領域,TDD還略微有些力不從心,或者說,至少,至今沒有看到太多比較好的實踐案例。比如:對於Database和UI。
對於資料庫開發的TDD,到目前為止面臨的主要挑戰是工具的支援。無論是自動化的迴歸測試工具,還是重構工具都還遠遠不夠成熟。
而對於UI的TDD,則是本文的主題。
TDD in HTML & JavaScript 概述
談到應用程式的UI,其實包括兩個方面的內容:一方面是純圖形的look & feel;另一方面,則是使用者和應用程式的互動。使用者和應用程式的互動往往同時導致圖形介面的變化,並且,轉換到新的互動行為。
由於工作實踐中主要是基於WEB的HTML和JavaScript的項目,這裡對TDD in UI的討論,將focus在基於HTML和JavaScript的UI。
同時,一般來講,WEB程式的表現層主要有用戶端代碼和服務端代碼,而服務端代碼,相對來說,更容易被測試。所以,本文討論的重點,主要focus在用戶端代碼。換句話說,這裡討論的TDD in HTML & JavaScript指的是對於用戶端的HTML和JavaScript的TDD。
TDD in HTML & JavaScript 之可行性
說到可行性,其實可以分兩個層面:理論上的可行性,和實際應用的可行性。
第一個問題是:純圖形的look & feel理論上可以進行自動化的測試嗎?答案幾乎是否定的。因此,主要用於呈現純圖形的HTML及CSS,也幾乎是很難自動化測試的。
那麼,使用者和應用程式的互動理論上是否可以進行自動化測試呢?答案毫無疑問是肯定的。
WEB互動的測試其實可以根據WEB程式的架構,分為兩種類型:
- 傳統的WEB程式主要基於服務端來呈現內容,使用者和頁面的互動,主要是get,post資料和頁面跳轉。因此,對應的測試方式,主要也是由測試載入器類比需要get或post的資料,並且跟蹤期望的頁面跳轉情況。這種情況下的測試其實相對簡單,因此本文不想過多討論。
- 當前的基於AJAX的WEB程式則很大程度上豐富了使用者和頁面互動的方式,使用者和頁面的互動,除了傳統的get,post資料和頁面跳轉,在頁面不重新整理的情況下,還通過觸發各種DOM事件,甚至直接觸發JavaScript方法的執行,由JavaScript來改變和呈現內容。此時,傳統的只能類比需要get或post的資料的測試載入器就無能為力了。此時由於所有的邏輯代碼都在JavaScript中,所以,本質上其實是需要對大量的JavaScript代碼進行測試。此正是本文希望討論的重點。
首先,針對JavaScript的自動化測試載入器其實已經有不少了,如:
Mock工具也有:
支援直接重構JavaScript代碼的工具相對比較少,提供的功能也都還非常弱:
從支援工具的現狀,可以說,影響TDD in JavaScript的實際可行性的因素之一是重構工具的缺乏。
不過,最近的情況有了一些改變,現在也出現了一些支援JavaScript重構的變通的解決方案,如:
- Script# - Write C# code,compile C# source code directly to JavaScript code
- jsc – Write any .NET code, convert .NET assembly to JavaScript, ActionScript, java or PHP code
這些方案的特點是,利用現有的IDE對流行的程式設計語言如C#原始碼的完善的coding,尤其是強型別,重構和測試的支援,讓開發人員寫C#,由工具轉換為可直接執行的,格式化的JavaScript代碼。除了充分利用IDE對流行語言的coding支援之外,這類方案的另一個好處是,相對於高薪聘請Senior的JavaScript開發人員,Junior的C#的開發人員要便宜得多,也易招得多,但得益於Script#,已經足夠能用他們熟悉的C#,寫出邏輯複雜和OO的JavaScript代碼,因此,開發成本被大大降低。
綜上所述,TDD in JavaScript不僅理論上是可行,實際應用上,也是有足夠的工具支援的。尤其是如Script#這樣的工具的出現,極大地提高了JavaScript代碼的開發效率。
TDD in JavaScript 之最佳實務
誰都希望能有最佳實務。什麼是最佳實務呢?有很多人見不得“best”,“最”這樣的詞,認為,這個世界上沒有“最”的東西。有嗎?當然有!我們首先要略為上升到哲學的高度,對於包含“最”這樣的詞彙的命題,如果想要為“真命題”,則必然是需要加上一個適當的前提條件的。
比如說:我說“我是這世界上最NB的人”。這毫無疑問是個假命題。因為,缺乏適當的前提條件。你可以自己做個練習,如果覺得這個命題假,想辦法給它加上更多的前提條件,一定能讓它變真。
所以,所謂最佳實務,指的是,對一個或者一類特定的問題,在一個相對確定的背景下,所能採取的實際處理的方案典範。加上前提條件,則“最佳實務”當然是存在的,也是值得討論的。
通過前面的章節,我們已經把本文重點討論的主題,限制到一個相對小的範圍,那就是對基於AJAX的WEB應用程式中的大量的JavaScript代碼,如何進行TDD?
並且,我們也收集了足夠的支援TDD需要的各種工具,包括自動化測試載入器,Mock工具和重構工具。在這些工具的支援下,很大程度上,WEB程式用戶端JavaScript代碼的TDD和服務端代碼的TDD,不應該有很大的區別。但同時,由於用戶端代碼的特殊性,自然也應該有一些用戶端指令碼代碼所特有的實踐模式。
以下首先列出本人推薦的一些實踐模式,希望大家能一起修正和補缺。
最佳實務一:應用MVC模式
在傳統的非AJAX的WEB程式中,JavaScript往往處於非常輔助性的地位。除了實現一些特效和資料驗證等協助工具功能之外,一個頁面的JavaScript代碼,恐怕屈指可數,自然無所謂測試,甚至是TDD了。
但是在現在的複雜的AJAX應用中,以往必須由多個獨立頁面的get,post和頁面跳轉才能組合實現的功能,通過JavaScript,可以在一個無需重新整理瀏覽器的頁面中,輕易實現,不但使用者體驗更佳,速度更快,對伺服器的負擔也更小。
此時,原本傳統WEB程式的服務端需要處理的問題,如資料繫結,事件綁定,邏輯控制等,需要在用戶端進行處理。也因此,原本為瞭解決WEB程式服務端代碼可測試性問題MVC模式,也就一樣可以良好的應用於用戶端。清晰的將JavaScript代碼分割成M,V,C,將能夠把相同的邏輯職責儘可能集中到一起來管理,從而極大地增加用戶端代碼的可維護性和可測試性。
下表簡單對比服務端和用戶端MVC下M,V,C的對應職責:
|
Model |
View |
Controller |
Server Side |
返回用於呈現頁面內容的資料的 Domain Objects |
代表了一個頁面的抽象,包括頁面的內容呈現,資料,事件定義 |
處理View上觸發的事件,擷取資料,更新View上的資料,觸發View的內容呈現 |
Client Side |
返回 JSON 資料的 Restful Services |
同上 |
同上 |
最佳實務二:應用依賴注入和IoC容器
應用MVC模式,本質上是抽象的邏輯職責上的解耦。而依賴注入和IoC容器則是代碼的物理依賴性上的解耦。儘可能的利用構造器注入,設值注入,介面注入或IoC容器來解除具體的實作類別之間的直接依賴,自然就能極大的大提高每個具體的實作類別的可測試性。
最佳實務三:應用模板引擎呈現主體內容
AJAX應用中的一個需要用戶端轉譯的View,必然需要呈現一些HTML,這些HTML往往需要根據Model返回的JSON資料動態構造。一般來講,我們會有三種方式來構造和呈現這些HTML:
- 在JavaScript中遍曆JSON資料,拼接HTML字串,呈現到頁面上;
- 在JavaScript中遍曆JSON資料,動態執行個體化DOM對象,通過DOM對象的方法,呈現HTML的DOM;
- 通過如JTemplate這樣的JavaScript模板引擎,將JSON資料繫結到一個HTML模板,由模板引擎呈現最終的HTML;
本最佳實務的建議內容就是,對於一個View的主體內容,應該儘可能的通過模板引擎來呈現。為什麼呢?因為,對於一個WEB程式來說,最不穩定的,會經常變化的部分,無疑是純圖形的HTML和CSS,使用模板引擎,將能夠使得這些HTML儘可能的集中,並且易於修改,也更易於HTML和JavaScript的整合。
最佳實務四:應用Script#
應用Script#好處前面已經提過了,這裡再簡單列舉一下:
- 充分利用現有的IDE對流行的程式設計語言如C#原始碼的完善的coding,尤其是強型別,重構和測試的支援;
- 相對於高薪聘請Senior的JavaScript開發人員,Junior的 C#的開發人員要便宜得多;
如反對,請列舉我不該用它的理由?
對於以上幾個最佳實務的應用執行個體,請參見我之前的文章:This is jqMVC# – CNBLOGS Google Tracer Sample。
歡迎補缺、指正!謝謝!