本文僅就單元測試而論,雖然是說的測試,但目的是驅動開發,不過也不是談測試驅動開發,更象是對測試驅動開發時TEST FIRST這個過程中如何保證測試程式碼的正確性的理解和想法,當然有一些,我認為是通用的,不管是不是測試優先。而我目前接觸最多的還是JAVA的單元測試,所以談的東西還是以JAVA為主,舉的例子都是和JAVA有關的。<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

另外前些天看到一個帖子有人問這樣的問題,想到當初自己剛接觸JUNIT單元測試時也有類似的困惑,現在有了一些經驗,所以寫下來,既是對自己經驗的總結,也是希望能有人相互討論提高。

首先是我認為要做到測試程式碼的正確性的幾個要點:

一、TEST FIRST

二、只寫出需求測試(注意,是目的碼需求,這個程式碼的使用者當然是自己了,^_^)

三、不要為了測試而測試(這句話是和一個朋友聊天時他說是他和Kent Back交流時Kent Back提醒他的,這裡我也不是很確定對這句話的理解的正確與否,因為理解一句話,上下文也是關鍵的,而我並不很瞭解我朋友同Kent Back談話的具體內容和過程,不過這裡還是作為一個要點談談自己的想法)

四、每次寫一點(原子級)測試

五、Clean code that works,(當然包括測試程式碼啦,^_^)

六、對於一個應用框架,最好是針對這個框架先寫一個測試框架(這其實是一個很具體的內容,不過現在JAVA在WEB方面用得很多,測試相對來說也比較難些,所以有這點)

七、時刻提醒自己TEST FIRST的目的。(我們的目的是驅動開發,而不是為了測試,呵呵,這點是前面第一點和第二點和起來一樣,之所以還要單獨列,只是再次提醒,所以這一點我後面不作詳細闡述)

八、偷懶是程式設計師的通病,但是小偷懶就別了。(我有這樣的觀點:程式設計師的水平高低,其實從他偷懶的程度上是可以看出來的……^_^)

在開始具體來說上述要點之前,我想先寫個例子,只是覺得應該寫個例子,^_^,我的文采實在不是很好的,所以憑感覺的。

一個例子:

我有一個專門用於將資料庫操作結果集(ResultSet)解析成一個DOM的Document物件的類,這個類可以根據給定一個XML配置模板中的一個定義節點(declare節點)的子節點(column節點)集合來解析ResultSet生成Document物件,其中每個column節點都定義了要從ResultSet中獲取的某一個欄位的屬性,包括欄位名、該欄位在展示是是否可修改(editable)、是否有格式化模式屬性(pattern)用於格式化該欄位的資料等等;為了做得更通用,也可以依據ResultSet返回的ResultSetMetaData物件來生成column節點,再由column節點獲取資料體,當然這樣做有很大的侷限性,比如該column節點的pattern、editable等等屬性的設定都不會太靈活。這個設計,其最大的靈活性被放在XML配置模板上,由模板的定義獲取資料,並且定義了資料的展示屬性,而一旦column節點是根據給定的ResultSet來自動生成時,靈活性大大折扣,雖然在大多數應用中,都不會使用由ResultSet來自動生成,但是如果一開始並不能確定定義列時卻是必須這樣做,特別是在ResultSet輸出的欄位數量是變化的時候。問題終於出來了,最近有一個應用就是這樣,首先是必須要使用從ResultSet獲取定義節點(column),然後,在完成了所有的程式碼後,發現給定ResultSet中都存在冗餘欄位,這個時候,沒辦法,只能是修改程式來適應它了。

在遇到這個麻煩,並確定必須修改自己程式碼後(老實說,第一反應當然是讓人修改SQL來去掉冗餘欄位了,因為最初的設計根本就是不能有冗餘資料的,不過確實是大家都有本難唸的經啊,SQL是不能改了,因為資料庫端的實現使用了一個穩定的公共實現,慶幸的是冗餘欄位是相同的),我腦袋裡蹦出的第一個念頭是新增一個事件監聽器,來監聽這個應用中生成資料部分(為了敘述方便,姑且叫“dataBuilder”吧)的程式碼,一旦資料生成就觸發ResultSet解析完成事件,然後我可以寫監聽器來處理解析完成的結果集(當然就是將冗餘的資料CUT掉啦),這樣以後再出現其它類似的狀況,我可以通過新增新的監聽器來過濾資料而不需要動原有的程式碼。SWEAT,看來這個想法還算可行,至少比寫一個子類看起來簡單多,以後修改也容易多,動手吧。(其實要注意這個事情的發生環境,首先是碼本來都OK了的,而後來突然發現這個問題,而這個問題是需要立刻修改掉的,所以沒有太多時間來仔細考慮,我總是犯這樣的錯誤。)

這個時候我有兩個選擇,一是直接就寫程式碼,一是先寫個測試。當然,我選擇的是先寫個測試,之所以擺明了這二個當然是為了比較了。先說如果我先寫程式碼,那麼我就直接進入了目標功能的角色,因為現在似乎目標很明確,我要給dataBuilder新增一個能處理監聽器的功能,在使用ResultSet解析器解析完成一個Document後觸發解析完成事件,通知所有註冊的監聽器,並將解析完成的結果通過事件物件傳遞給監聽器進行處理。在以前,我會立刻想到如何新增事件,如何處理事件列表,還有兩個必要的介面,一個是監聽器介面,一個是事件介面,一些簡要的構思之後,不用多少時間就可以完成這些工作,然後就開始除錯。

上面那麼說,主要是為了對比,現在我是先寫測試的。當然,如上所述目標現在似乎也很明確的,那麼無論如何寫個測試類吧,在寫上必要的to do list之後,我開始想如果現在程式碼寫完了,我要怎樣來使用它呢。哦,對了,測試這個之前還要寫個測試使用的監聽器實現,這個實現可以很簡單,把解析結果Document幹掉好了,呵呵,驗證結果還更容易,那就把它所有節點remove掉好了,^_^。(注:這裡的測試還沒有到主功能,只是先做測試監聽器部分)不過這個時候,我突然覺得目前這個設計似乎還是有些麻煩(懶惰是程式設計師的通病,sweat,每次想偷懶都想起這句),要寫監聽器,還要讓dataBuilder處理監聽列表觸發事件,雖然讓過濾資料操作可以很獨立地新增,而監聽器的註冊也可以通過XML配置檔案來完成,但還是顯得多餘,好像目前這個需求只要一個Decorator模式,寫一個修飾類來修飾ResultSet解析器就差不多了,以後需要新的功能,換Decorator就可以了,也可以相互巢狀來完成多個功能,這樣的話,就不需要對dataBuilder動太多手腳了。sweat,還好自己沒動手瞎忙(無論如何,測試優先讓我重新認識自己的設計,以及目標,於是我義無反顧地拋棄了原來的想法)。新目標出現了,看來我需要一個解析器介面實現的Decorator類(注:這個ResultSet解析器類原本就是一個介面ResultSetParser的實現),我可以先寫一個擴充套件解析器介面的抽象類來包裝下,以後的Decorator實現都從這個抽象類繼承,直接實現修飾內容就可以了(實際我一直認為,在Decorator模式中所有Decorator實現類都有一個父抽象類繼承自修飾目標類的介面,其最主要的目的是使Decorator實現類功能更清晰,因為實際這個抽象類要包裝的東西其實很少,這些移到子類中也完全可以,這樣的話子類就是直接實現修飾目標類的介面了,效果一樣,所以我認為這裡有一個這樣的抽象類統一由所有修飾類繼承,更主要的是宣佈,一個修飾目標類介面的實現類它是一個修飾類,這在閱讀程式,以及使用該API上可以達到很好的效果。)。OK,就這麼辦(我總是很容易下決定呢,^_^)。

那麼現在我該怎麼來測試了呢,當然我同樣需要一個Decorator類實現來作測試(還是按原定計劃,把Document的子節點remove掉),準備好測試資料後,當然是檢查結果集了,但是這個時候麻煩又來了,我不但要檢查column節點是否被正確過濾,還要檢查資料體是否正確,這樣的話,如果我使用硬編碼斷言來測試,那麼結果一定是一堆的斷言,當然,我可以先寫好一個正確結果集的XML檔案,然後讀取它來和處理結果來比較(唉,Document物件沒有實現equals),不過這還不是最麻煩的,最麻煩的是,這樣的測試將依賴資料庫環境,這樣的話我又要儲存資料庫環境才能保證在任何情況下我可以重現我的測試,這無疑是最麻煩的,老實說,以前之所以會不寫測試(即使是現在也有時候不寫,雖然我也清楚寫測試的好處),主要就是資料庫環境很難在任何時候很容易地建立(我有試過DBUNIT,它也不是很好,搭建環境的程式碼也不少),我暈(想偷懶好難啊,難道又偷偷地不寫測試不成?)。

寫測試是一個很奇怪的過程,大腦的思維方式和直接寫應用程式碼好像有些不同,呵呵,沒搞清楚,我想這是咱和大師的區別吧,還只是只可意會不可言傳,我一直的觀點是,真正懂的人是能將模糊的東西解釋成清晰的概念的。無論如何在我準備開始動手寫這痛苦的測試程式碼時,我又有新想法了,為什麼不在開始就不獲取冗餘資料呢,雖然Decorator模式在這裡的應用很具有吸引力,因為它本身畢竟夠簡單明瞭,不過,同時想到Document物件本身的一些特點,首先它是大物件(呵呵,感覺上DOM的物件都好大哦,但其實還沒有真正理解它到底是怎麼個大法),其次,如果是過濾資料,那麼勢必要遍歷該Document的節點,這樣的話似乎處理起來的效率不怎麼樣,這怎麼都不如在一開始就不從ResultSet中獲取冗餘資料來得簡單明瞭。於是想到,在自動生成column節點上現在還是很弱的,可以用Builder模式來重構它,這樣的話,可以通過替換column節點的builder來實現過濾,而且目前來說,也確實是只有自動生成column節點的情況才需要過濾冗餘資料,所以也不需要考慮由XML配置模板定義好columns節點的狀況;而原來預設的生成方式可以立刻就被提煉出來作為預設builder;這樣新增的程式碼實際也很少,我想幾乎更少些吧,因為原來在處理冗餘資料時需要遍歷Document,而現在只要認準了從ResultSetMetaData獲取的欄位名,對需要過濾的欄位不生成column節點就Ok,而有新的變化出現時,只要替換重新實現builder介面就可以了。當然了,原來這個ResultSet解析器是有測試類的,也有相關的測試,那麼將原來生成column部分程式碼提煉出來作為預設的builder後,可以跑以前的測試,啊哈哈哈哈,這幾乎不費吹灰之力,ECLIPSE的重構功能很容易將生成column的程式碼提煉成一個方法,然後把這個方法提升成一個介面,接著如此如此(不用說了)就弄好了一個預設builder實現,然後解析器加個builder類屬性,同時生成它的set方法,一切ko後跑測試(這裡都是工具自動做的多,我這一步跨的是會大些的,不過多跑測試總是沒什麼壞處的,反正跑一次也就幾秒中,也許還不用呢,^_^),一路綠燈。重構完成,sweat(注意到了吧,到目前為之,其實我沒有寫測試,而是使用了原來的測試程式碼,而程式碼部分也多是自動生成的。最重要的是,這裡使用builder模式,其介面也被測試過了,所以後面加的builder實現就可以不考慮這點了,而只是完全的功能需求測試,功能需求總是可以很單一的,自然就簡化了測試程式碼)。

現在又回到開始了,我的目標又換了,呵呵,新目標:一個builder介面實現,過濾冗餘的欄位。如何測試呢?一個ResultSet做輸入引數是必要的,輸出的話要沒有冗餘資料,實際應用中要過濾的欄位名是固定的(呵呵,別忘了我的最初的目的,是過濾由於SQL查詢使用公共介面導致有相同的冗餘欄位出現。),我可以SELECT出來一個空結果集,其欄位都是我要過濾的那些欄位名(這裡,如前所述,資料體不是我要關心的內容,因為資料體的是根據column節點生成的,所以我只要檢查column節點對不對就可以了,這樣也簡化了我的測試程式碼),當然,在這些之前是重置ResultSet解析器的builder物件為將要實現的目的碼類,很快就完成了所有的程式碼,跑測試,出錯,修改,跑測試,Kent Back的測試模式顯得那麼簡單,最後,終於一路綠燈了。呼………..

呵呵,當然不忘了最後一步,Clean code that works。我對目前這個狀態下去提煉原始碼,有個比喻:關起門來打狗。這時的重構真可以說甕中捉鱉十拿九穩,最不濟也只是回到原地,不過通常的結果總是出人意料的要。不管如何,開始重構,幾個關鍵重構:首先預設的builder可以用一個Singleton模式,當然了,實現過濾冗餘欄位的builder也是,因為這個生成column節點的程式碼是不受多執行緒影響的;然後過濾冗餘欄位的builder實際可以是一個預設builder的Decorator,三下五除二搞定。最後測試通過,CHECK IN程式碼。

這是我的一次經歷,可以看到,早先的測試程式碼也起了作用,所以,只要程式碼還要跑,那麼測試程式碼永遠也不會多餘。而要保持清晰的測試程式碼,不僅需要清晰需求分析、設計以及良好的原有目的碼,一點點偷懶的精神,也是必要的,^_^。而在需求分析和設計這裡,測試優先有一種不可抗拒的力量讓你深入需求,特別是讓你作為你的目的碼的使用者來考慮這些程式碼的使用。本來這個例子只想提下,沒想到寫出來這麼多,sweat。

OK,下面開始逐一討論:

一、TEST FIRST

TEST FIRST其核心不在“TEST”,而是“FIRST”。測試驅動開發的目的是開發,而測試優先是其過程,從測試開始,以測試結束,中間是小增量的迭代開發,測試程式碼和開發程式碼同步進行(注:其實我認為測試程式碼也是開發程式碼,不過為了區分這2者,下面所說到的開發程式碼都指不包括測試程式碼部分;另外,這句話本身要是仔細扣是有問題的,並不全面,只是測試驅動開發的一個方面來說)。

不是說要保證測試程式碼的正確性就要TEST FIRST,而是TEST FIRST可以最大限度地保證測試程式碼的正確性。(這裡要說回程式碼本身,無論是測試程式碼還是開發程式碼,無非是一種開發語言的程式碼,要保證程式碼的正確性,我認為主要是良好的設計、清晰的結構以及語言的合理使用,其中保持程式碼結構清晰是最容易做到的,簡單的縮排,統一的編碼風格基本就算合格了。對於測試程式碼,也是一樣的道理。)如果不先寫測試,那麼一定是有目的碼了(廢話好像,^_^),這在寫測試程式碼時必然會受到限制,別說自己可以不理會寫好的目的碼(我想90%的人還是比較難做到的吧,都寫好了,還改來改去,而且一個介面一旦公佈出去,由不得你想改就改),寫測試的時候,束手束腳,這不能動,那不能改,你怎麼辦,當然是按照自己寫開發程式碼的思路來寫測試了(呵呵,好像如果是先寫了開發程式碼而後寫測試程式碼,測試程式碼的目的也多是為了驗證開發程式碼的正確性而已,至少以前我有這樣做時是這樣的狀況),於是你的思維被禁錮了,測試程式碼的好壞(這裡的壞不是說不能用,^_^),就由開發程式碼控制了。說回到我舉的例子,如果開始的時候確實是因為時間關係、以及需求的突發性改變導致沒有很細緻地考慮如何修改設計(實際上我認為這樣的狀況實在太多,我接觸的使用者,有很確定地提出某個需求,而最後由於來看演示的使用者完全否決該需求而導致完全推翻重來的,不過要說的是,我面對的使用者實在沒法子,人改需求的使用者比當初提需求的使用者官大,sweat,不改不行,時間還不能拖,原因就不細說了),直接就由開始的構思來寫好目的碼先,那麼會是什麼樣的局面?首先是時間已經花進去了(呵呵,單就這個例子當然其實也沒多少時間的,是相對來說的),而到開發程式碼完成要寫測試的時候必然導致寫測試的目的是驗證這段程式碼的正確與否,這樣的話,不用說,準備好輸入資料,驗證輸出資料嘛,哪還管那麼多?(我認為是這樣的,一旦確定了目標,而且還是那麼實在的目標,做起事來的時候,腦袋裡早就計劃好了,就沒有餘地來考慮其它東西了,更不要說跨出這件事情考慮做另一件事情了)。而採用測試優先的方式的話,其結果和過程我在例子裡面也寫的很詳細了,沒有目的碼寫測試其實是很輕鬆的事情,因為現在什麼都還沒有,自己想怎麼就怎麼樣,想讓測試程式碼清晰、簡單明瞭,那還能複雜到那裡去?

所以說,TEST FIRST是提高測試程式碼正確性的一個很好的途徑。這裡先寫開發程式碼後寫測試程式碼的方式,其實說白了,是我想說的第三點,那樣的測試,真的就很容易成了為了測試而測試的程式碼(記憶裡,以前我這麼做的時候,還都是為了測試而測試,呵呵,不知道其他朋友會如何)。

二、只寫出需求測試(注意,是目的碼需求,這個程式碼的使用者當然是自己了,^_^)

前面說到,要保證測試程式碼的正確性,從保證程式碼本身的清晰來入手,只寫需求測試也是一個道理,不過要把它放在這裡,主要也還是因為自己以前寫測試的時候,往往都很容易忘記這一點,簡單的比方說,當一個測試會牽涉多個類的時候,本來其實也很明確的,單元測試嘛,你測試你的目標類就OK了唄,但是每個人的認識不同,寫測試的水平不同,更在於小心謹慎,總是在寫目標類測試的時候,想這個地方雖然是A類做的事情,不過我也連帶著加個斷言看下A做對了沒有吧,反正也就多加一句,以防萬一嘛(太小心了,其實過界了都不知道)。別小看一行程式碼,我的感覺,超過20行(空行不算,sweat)的一個方法在你寫完這個方法幾個月後回來看,暈的機率很大,至少要立刻明白這個方法的內部結構是基本不能。積少成多,如果目標類關聯了A、B、C、D、E五個甚至更多,多出來的恐怕就不是一、二行程式碼了,而且你一次小心一定會多次小心的,比如,在一個測試用例中(我所指的測試用例不是指TESTCASE類,而是該類中的一個TEST方法)你的斷言中包含了對A的一個操作結果的測試,那麼在另一個測試用例中,如果還用到A的這個操作,我肯定你還會寫這個斷言,因為你第一次的小心不是偶然。人說,小心使得萬年船,不過,杞人憂天就過猶不及了。而寫測試的時候,是很容易就會犯這樣的錯(唉,其實是我當初很容易犯這樣的錯,sweat)。

所以,職責分明,只寫你需要的你該做的測試,別一開始就懷疑這個懷疑那個,處處小心,步步為營。我們要如何使用我們的程式碼,那麼就寫那樣的測試,現在我都是拿測試程式碼當例子,寫了一個公共模組,除了有文件以外,都是建議要是覺得看文件麻煩就看測試程式碼如何使用,或者你乾脆就拷貝我的測試程式碼過去改下用好了。

三、不要為了測試而測試(這句話是和一個朋友聊天時他說是他和Kent Back交流時Kent Back提醒他的,這裡我也不是很確定對這句話的理解的正確與否,因為理解一句話,上下文也是關鍵的,而我並不很瞭解我朋友同Kent Back談話的具體內容和過程,不過這裡還是作為一個要點談談自己的想法)

不要為了測試而測試,^_^,雖然說要寫測試,不過大家都是開發的,如果你的測試程式碼的目的成了測試,我還是會說你這就不夠“專業”了。(這句有點耳熟,星星:拜託,雖然大家都是中國人,你要是抄我臺詞,照樣要告你剽竊)

其實開始我聽朋友講這句,似懂非懂,因為畢竟只是一句沒頭沒尾的話,聽過就算了。但是細細品了,確覺得有些道理(^_^,不知道我的想法是不是Kent Back說這句話的本意)。言歸正傳,開發人員的測試程式碼,還是不要以測試目的碼為目標的好,這樣才能更好的確保測試程式碼的正確性。

這裡要澄清的是,不是說測試程式碼就不要去拿來測試了,呵呵,測試程式碼的結果(無論是否測試先行)其實都是一樣的,都是可以自動(或者半自動,^_^)執行的測試,我說了,是寫測試的目的,由於你的目的不同,中間的過程也是大不相同的,對於開發人員來說,開發程式碼是最終目的(什麼需求分析、系統設計啦等等等等,還不是為了能滿足使用者需求的程式碼出來給使用者跑嘛,哦,當然,最最終目的,咱還是靠這個養家餬口,sweat),所以開發過程中的單元測試程式碼,它只是輔助我們開發的(這點好像說了好多遍了,sweat,別丟我臭雞蛋),所以這些測試程式碼的最終目的也是為了開發程式碼,它和測試人員寫的測試程式碼有本質的區別(測試人員要是來看我這篇文章,嘿嘿,SORRY啦,真不是為測試寫的)。

說得好繞口,不過,只有清楚了這點,那麼前面2點才會站得住腳,如果一開始得測試程式碼就只是為了測試目的碼得功能,那麼TEST FIRST就是笑話了,連目標都沒有怎麼可能測試,而根據需求寫測試,根本不管內部邏輯以及邊界條件等等等等什麼這覆蓋那覆蓋的測試,測試人員一定說我瞎掰了,這也能拿來測試?

所以,要保證測試程式碼的正確性,別為了測試而測試,只有不為了測試而測試,你才會真正做到第一、二點。(好像這點應該擺第一去,sweat,不過既來之則安之落)

四、每次寫一點(原子級)測試

這一點,其實說白了,是XP中的小增量迭代,測試驅動開發強調雖然是測試優先,但是測試開發並行過程中相互的互動作用是很關鍵的。

一個很籠統的測試用例,既測試這個又測試那個,難免會複雜起來(量的積累帶來質的變化),所以最好是一個測試用例只針對一個需求寫測試,少量多次嘛。新增一點測試,執行失敗,修改程式碼,執行測試,直到成功,然後重構程式碼,最後測試通過,是測試驅動開發的一個小迭代週期,當然迭代週期的控制是可以因人而異的。

每次寫的測試越少,那麼表示你關注的問題或者需求就越少,那麼精力就越集中,相對的測試的程式碼量也越少,大問題通常都可以通過將其拆分成多個小問題分別解決來得到解決,如果覺得測試程式碼太複雜,自然也可以通過拆分複雜的測試程式碼為多個小的相對簡單的測試程式碼段來縮小其複雜性。

五、Clean code that works,(當然包括測試程式碼啦,^_^)

再好的設計,架不住你混亂的程式碼,最簡單的,沒有良好縮排格式的程式碼,看起來真是惡夢,現在工具這麼好,都能自動化了,不過我還是能在工作中看到很多沒有格式化好的程式碼,sigh,最起碼的要求都做不到,更不要說讓他去重構程式碼了,估計寫測試也是沒戲,TEST FIRST更是免談了。

所以別看這句話簡單,做到了再體會才能理解。(這裡又要嘮叨下TEST FIRST,如前所述,我其實開始的時候是先寫開發程式碼後寫測試的,每次都很猶豫,是不是先寫測試,而很多時候都架不住自己腦袋裡那個已經“成型”的設計,要立刻寫開發程式碼,測試嘛,總是認為後面再來也一樣,不過在我多次逼自己先寫測試之後,經歷感覺完全不同,TEST FIRST根本不是原先自己所想的那樣簡單,所以,雖然我這裡說了一堆它的好處,還是希望沒有體驗過的朋友別管那麼多,試試先,一定會喜歡上它的,不過,要真正到處都使用它,還是要提高寫測試程式碼水平的,因為測試程式碼不是那麼簡單哦,特別是和資料庫和WEB搭上關係後)。

好了,這一點,其實我也不用再詳細說了,其實大家瞎子吃餛飩心裡有數。至於怎麼做到,嘿嘿,看看《測試驅動開發》吧,也許會有體會的,而我這裡就超出範疇了,關於這點,我也就此打住,因為人大師的金玉良言更精闢更有說服力,我也不想在這裡做轉貼。

六、對於一個應用框架,最好是針對這個框架先寫一個測試框架(這其實是一個很具體的內容,不過現在JAVA在WEB方面用得很多,測試相對來說也比較難些,所以有這點)

這一點是說到應用上了,^_^,其實也許這才是一些朋友希望交流的東西,因為開始我學寫測試的時候,真不是我不想寫啊,而是實在不知道怎麼寫,對一個複雜的應用,比如前端WEB,後臺資料庫,只要和這2者關聯起來,再簡單的需求寫起測試來也是一大堆程式碼,有的甚至就根本不知道怎麼測試。我現在也還是對資料庫端的測試很懼怕,希望藉此有朋友能討論下。

這裡,我的觀點是很明確的,一個應用框架,要有針對這個框架的測試框架來支援,才能讓寫出來的測試程式碼清晰以保證其正確性。測試框架的目的是要保證測試環境的正確搭建,這裡說說題外話,測試環境,我認為是測試的關鍵,也是單元測試中最難的一個方面。對於一個介面的測試(純粹的介面,不是實現類),可能就需要寫一個擴充套件這個介面的實現類來輔助測試(我舉的例子中,在開始考慮使用Decorator首先就是考慮的這個模式應用後的介面測試,這樣我在測試具體的Decorator實現時才能拋開這個模式而單獨測試實現類,而最後在column節點處理時用builder模式時,其實是拿預設的builder來做了這個測試的),這樣做是我說的第二點;應用框架通常由介面架構,抽象出具體應用的介面,而具體應用則通過擴充套件該介面來實現功能,比如常見的WEB框架Structs,它的ACTION就是這樣的介面,要做好對它的測試,框架本身的測試必不可少,但具體應用時其實和框架關係不大,只要製作符合要求的ACTION就可以了,這樣的話,對ACTION的測試才是一般應用程式設計師要考慮的。對ACTION的測試,一種簡單的方式是將ACTION中的要測試程式碼和STRUCTS框架隔離,這樣就不需要訪問STRUCTS框架,直接測試,但是,這樣做其實不完整(不過這樣做也有好處,就是將邏輯程式碼強制寫在其它地方,而不寫在ACTION中,當然ACTION本身就不是用來寫這些的,不過我看到的,很多程式設計師其實是習慣於直接在ACTION中寫業務邏輯程式碼的,這樣的好處很少,就是眼前少寫幾行程式碼,但是以後就不見得了,這是我的第七點,大偷懶和小偷懶的區別),最好的狀況是完整地測試ACTION類,從呼叫它開始,但是這樣地話,準備測試環境會很麻煩,準備一個HTTP的REQUEST物件不是隨便可以NEW出來的,會讓人望而卻步,要是隻寫一、二個這樣的測試也就罷了,實際確實成百上千的,所以這個時候就需要一個測試框架了(呵呵,忽然想到,開始說測試框架,可能會有人誤會,說是測試應用框架本身的程式碼,那樣的測試當然不可少,不過這裡說的測試框架,是指標對應用框架開發時要寫的TESTCASE的框架),對於WEB測試有HTTPUNIT是不錯的選擇,不過還是比較低階的(呵呵,這裡和語言一樣的比喻,JAVA之類是高階語言,不是說它比彙編就高階),要針對STRUCTS框架寫一個測試框架(現在有開源的這個框架),讓測試環境的搭建工作簡化到最少,這樣真正寫測試時才能只關注需求部分的測試程式碼,而測試程式碼本身也看起來不那麼龐大。

七、懶惰是程式設計師的通病,但是小偷懶就別了。(我有這樣的觀點:程式設計師的水平高低,其實從他偷懶的程度上是可以看出來的……^_^)

第六點中已經帶到了這個話題,其實只是調侃得說說自己的想法,不能算很正式的,因為這點其本質和測試沒有一點關係。

我很懶的,要不然不會選擇這個職業,因為程式可以自動幫我做事,這樣我就可以只是輕鬆地看著了,^_^。

但是懶惰有很多種,有一種最直接的,只顧眼前的懶惰,還是拿不格式化程式碼來說,寫的時候很隨意,是好像讓你少做了很多事,但是讓看的人,讓以後自己看的時候(特別是在查錯時),付出更高的代價,我想,無論你在下次回頭看這個程式碼的時候,理解它的時間是多花了多麼少的時間,也比不上選個選單自動格式化好程式碼花的時間少的。為變化而設計,正是為了將來能夠更偷懶。有一個觀點,現在我也想不起來是哪裡看到了的,似乎哪裡都有(《重構》啦、XP啦、測試驅動開發啦等等),就是在給一個已有模組新增新的功能前先重構已有程式碼,讓已有程式碼適合於新增這個新的功能(好像第一次看到這個應該是《重構》一書),重構不是說讓程式碼更好看而已,其目的就是為了適應變化,變化本來顯得很大,但是可以通過重構來改善已有程式碼,讓已有程式碼適合於新變化的新增,那麼變化的複雜性就被縮小了。

所以我說,小懶惰就免了,別影響大局,程式碼清晰、必要的重構,不作這些只是小懶惰,它們是以將來可能更大的付出為代價的。測試程式碼嘛,也只是程式碼而已。

好了,終於寫完了,中間幾點難免有些雷同了(Clean code that works好像就可以涵蓋所有了),還是總結下,思來想去,還是這句了:Clean code that works。大師的話就是精闢啊。不過,仔細想想,上面說的幾點,好像都是沒有說如何做到的,只是說了做到這幾點可以極大得保證測試程式碼的正確性,但是這些關鍵點,要如何做到,我想大家還是多看看書吧,《測試驅動開發》、《重構》還有《極限程式設計-擁抱變化》這些都是難得的好書,我想,相對其它的書,這3本關鍵並不在於其有什麼很高深的理論或者技術,而是它們都是指導程式設計師實踐,如何從小處著手做程式,養成好的程式設計習慣,也更象是這些大師們自己手把手教咱寫程式一樣,難得的是文筆還很好,敘述得淺顯易懂。

第一次在CSDN寫這麼長的東西,算是篇“處理”作了,因為在網上寫東西還是顯得匆忙(雖然寫完看了兩遍,作了些修改),疏漏難免(特別是寫著寫著總會有點跑題,blush),而且寫文章來說,實在難以測試先行,難以保證質量,歡迎指教,也希望能相互討論提高。