討論: TDD in HTML & JavaScript 之可行性和最佳實踐

題外話

昨天就想發起這個話題的討論,只是覺得對於討論的支持,博客園現有的功能天然似乎還不能很好的支持。所以有了突然發現想在博客園發起一個有價值的討論其實很難一文。亞歷山大同志提到“博客園的討論需要發起爭議性話題,比如 .net sucks之類”。回顧如關於近期C#大論戰的迴應這樣的近期引起討論的焦點話題,貌似確實如此。深以爲嘆。近期的C#大論戰是幸運的,儘管中間還是參雜了很多口水,李建忠老師的加入,一定程度上最終將話題引向了正確的方向。幸哉。我這個圍觀羣衆也從中獲益良多。不僅僅是對於這個技術話題正確理解,還包括李老師在他的博客最後提到的:拋掉“非要辯個勝負,分個高低”的怪誕氛圍,而是來一些紮紮實實的技術說理過程,相信會更有意義——如是,則國內技術社區成長可待。

言歸正傳,還是儘快開始我想討論的主題,且不管討論最終成效如何,既然發起討論,還是先儘可能分享自己的想法,以示誠意。

TDD的背景

自從03年Beck正式提出(事實上在00年,Beck提出eXtreme Programming時,就已經提出了這個詞)Test-driven design/development這樣一個基於測試優先、重構和迭代的革命性的開發方法以來,無數的實踐已經證明,對於適合進行TDD的領域,TDD能夠極大地提高代碼的可維護性和開發效率。

TDD的基本流程圖如下:

http://upload.wikimedia.org/wikipedia/en/9/9c/Test-driven_development.PNG

在這樣一個迭代的流程中,在寫任何的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程序的架構,分爲兩種類型:

  1. 傳統的WEB程序主要基於服務端來呈現內容,用戶和頁面的交互,主要是get,post數據和頁面跳轉。因此,對應的測試方式,主要也是由測試工具模擬需要get或post的數據,並且跟蹤期望的頁面跳轉情況。這種情況下的測試其實相對簡單,因此本文不想過多討論。
  2. 當前的基於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

 

歡迎補缺、指正!謝謝!

<script type="text/javascript"> if ($ != jQuery) { $ = jQuery.noConflict(); } </script>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章