本文僅就單元測(cè)試而論,雖然是說(shuō)的測(cè)試,但目的是驅(qū)動(dòng)開(kāi)發(fā),不過(guò)也不是談測(cè)試驅(qū)動(dòng)開(kāi)發(fā),更象是對(duì)測(cè)試驅(qū)動(dòng)開(kāi)發(fā)時(shí)TEST FIRST這個(gè)過(guò)程中如何保證測(cè)試代碼的正確性的理解和想法,當(dāng)然有一些,我認(rèn)為是通用的,不管是不是測(cè)試優(yōu)先。而我目前接觸最多的還是java的單元測(cè)試,所以談的東西還是以JAVA為主,舉的例子都是和JAVA有關(guān)的。
另外前些天看到一個(gè)帖子有人問(wèn)這樣的問(wèn)題,想到當(dāng)初自己剛接觸JUNIT單元測(cè)試時(shí)也有類(lèi)似的困惑,現(xiàn)在有了一些經(jīng)驗(yàn),所以寫(xiě)下來(lái),既是對(duì)自己經(jīng)驗(yàn)的總結(jié),也是希望能有人相互討論提高。
首先是我認(rèn)為要做到測(cè)試代碼的正確性的幾個(gè)要點(diǎn):
一、TEST FIRST
二、只寫(xiě)出需求測(cè)試(注重,是目標(biāo)代碼需求,這個(gè)代碼的用戶(hù)當(dāng)然是自己了,^_^)
三、不要為了測(cè)試而測(cè)試(這句話是和一個(gè)朋友聊天時(shí)他說(shuō)是他和Kent Back交流時(shí)Kent Back提醒他的,這里我也不是很確定對(duì)這句話的理解的正確與否,因?yàn)槔斫庖痪湓挘舷挛囊彩且Φ模也⒉缓芰私馕遗笥淹琄ent Back談話的具體內(nèi)容和過(guò)程,不過(guò)這里還是作為一個(gè)要點(diǎn)談?wù)勛约旱南敕ǎ?/P>
四、每次寫(xiě)一點(diǎn)(原子級(jí))測(cè)試
五、Clean code that works,(當(dāng)然包括測(cè)試代碼啦,^_^)
六、對(duì)于一個(gè)應(yīng)用框架,最好是針對(duì)這個(gè)框架先寫(xiě)一個(gè)測(cè)試框架(這其實(shí)是一個(gè)很具體的內(nèi)容,不過(guò)現(xiàn)在JAVA在WEB方面用得很多,測(cè)試相對(duì)來(lái)說(shuō)也比較難些,所以有這點(diǎn))
七、時(shí)刻提醒自己TEST FIRST的目的。(我們的目的是驅(qū)動(dòng)開(kāi)發(fā),而不是為了測(cè)試,呵呵,這點(diǎn)是前面第一點(diǎn)和第二點(diǎn)和起來(lái)一樣,之所以還要單獨(dú)列,只是再次提醒,所以這一點(diǎn)我后面不作具體闡述)
八、偷懶是程序員的通病,但是小偷懶就別了。(我有這樣的觀點(diǎn):程序員的水平高低,其實(shí)從他偷懶的程度上是可以看出來(lái)的……^_^)
在開(kāi)始具體來(lái)說(shuō)上述要點(diǎn)之前,我想先寫(xiě)個(gè)例子,只是覺(jué)得應(yīng)該寫(xiě)個(gè)例子,^_^,我的文采實(shí)在不是很好的,所以憑感覺(jué)的。
一個(gè)例子:
我有一個(gè)專(zhuān)門(mén)用于將數(shù)據(jù)庫(kù)操作結(jié)果集(ResultSet)解析成一個(gè)DOM的Document對(duì)象的類(lèi),這個(gè)類(lèi)可以根據(jù)給定一個(gè)xml配置模板中的一個(gè)定義節(jié)點(diǎn)(declare節(jié)點(diǎn))的子節(jié)點(diǎn)(column節(jié)點(diǎn))集合來(lái)解析ResultSet生成Document對(duì)象,其中每個(gè)column節(jié)點(diǎn)都定義了要從ResultSet中獲取的某一個(gè)字段的屬性,包括字段名、該字段在展示是是否可修改(editable)、是否有格式化模式屬性(pattern)用于格式化該字段的數(shù)據(jù)等等;為了做得更通用,也可以依據(jù)ResultSet返回的ResultSetMetaData對(duì)象來(lái)生成column節(jié)點(diǎn),再由column節(jié)點(diǎn)獲取數(shù)據(jù)體,當(dāng)然這樣做有很大的局限性,比如該column節(jié)點(diǎn)的pattern、editable等等屬性的設(shè)置都不會(huì)太靈活。這個(gè)設(shè)計(jì),其最大的靈活性被放在XML配置模板上,由模板的定義獲取數(shù)據(jù),并且定義了數(shù)據(jù)的展示屬性,而一旦column節(jié)點(diǎn)是根據(jù)給定的ResultSet來(lái)自動(dòng)生成時(shí),靈活性大大折扣,雖然在大多數(shù)應(yīng)用中,都不會(huì)使用由ResultSet來(lái)自動(dòng)生成,但是假如一開(kāi)始并不能確定定義列時(shí)卻是必須這樣做,非凡是在ResultSet輸出的字段數(shù)量是變化的時(shí)候。問(wèn)題終于出來(lái)了,最近有一個(gè)應(yīng)用就是這樣,首先是必須要使用從ResultSet獲取定義節(jié)點(diǎn)(column),然后,在完成了所有的代碼后,發(fā)現(xiàn)給定ResultSet中都存在冗余字段,這個(gè)時(shí)候,沒(méi)辦法,只能是修改程序來(lái)適應(yīng)它了。
在碰到這個(gè)麻煩,并確定必須修改自己代碼后(老實(shí)說(shuō),第一反應(yīng)當(dāng)然是讓人修改SQL來(lái)去掉冗余字段了,因?yàn)樽畛醯脑O(shè)計(jì)根本就是不能有冗余數(shù)據(jù)的,不過(guò)確實(shí)是大家都有本難念的經(jīng)啊,SQL是不能改了,因?yàn)閿?shù)據(jù)庫(kù)端的實(shí)現(xiàn)使用了一個(gè)穩(wěn)定的公共實(shí)現(xiàn),慶幸的是冗余字段是相同的),我腦袋里蹦出的第一個(gè)念頭是添加一個(gè)事件監(jiān)聽(tīng)器,來(lái)監(jiān)聽(tīng)這個(gè)應(yīng)用中生成數(shù)據(jù)部分(為了敘述方便,姑且叫“dataBuilder”吧)的代碼,一旦數(shù)據(jù)生成就觸發(fā)ResultSet解析完成事件,然后我可以寫(xiě)監(jiān)聽(tīng)器來(lái)處理解析完成的結(jié)果集(當(dāng)然就是將冗余的數(shù)據(jù)CUT掉啦),這樣以后再出現(xiàn)其它類(lèi)似的狀況,我可以通過(guò)添加新的監(jiān)聽(tīng)器來(lái)過(guò)濾數(shù)據(jù)而不需要?jiǎng)釉械拇a。SWEAT,看來(lái)這個(gè)想法還算可行,至少比寫(xiě)一個(gè)子類(lèi)看起來(lái)簡(jiǎn)單多,以后修改也輕易多,動(dòng)手吧。(其實(shí)要注重這個(gè)事情的發(fā)生環(huán)境,首先是碼本來(lái)都OK了的,而后來(lái)忽然發(fā)現(xiàn)這個(gè)問(wèn)題,而這個(gè)問(wèn)題是需要馬上修改掉的,所以沒(méi)有太多時(shí)間來(lái)仔細(xì)考慮,我總是犯這樣的錯(cuò)誤。)
這個(gè)時(shí)候我有兩個(gè)選擇,一是直接就寫(xiě)代碼,一是先寫(xiě)個(gè)測(cè)試。當(dāng)然,我選擇的是先寫(xiě)個(gè)測(cè)試,之所以擺明了這二個(gè)當(dāng)然是為了比較了。先說(shuō)假如我先寫(xiě)代碼,那么我就直接進(jìn)入了目標(biāo)功能的角色,因?yàn)楝F(xiàn)在似乎目標(biāo)很明確,我要給dataBuilder添加一個(gè)能處理監(jiān)聽(tīng)器的功能,在使用ResultSet解析器解析完成一個(gè)Document后觸發(fā)解析完成事件,通知所有注冊(cè)的監(jiān)聽(tīng)器,并將解析完成的結(jié)果通過(guò)事件對(duì)象傳遞給監(jiān)聽(tīng)器進(jìn)行處理。在以前,我會(huì)馬上想到如何添加事件,如何處理事件列表,還有兩個(gè)必要的接口,一個(gè)是監(jiān)聽(tīng)器接口,一個(gè)是事件接口,一些簡(jiǎn)要的構(gòu)思之后,不用多少時(shí)間就可以完成這些工作,然后就開(kāi)始調(diào)試。
上面那么說(shuō),主要是為了對(duì)比,現(xiàn)在我是先寫(xiě)測(cè)試的。當(dāng)然,如上所述目標(biāo)現(xiàn)在似乎也很明確的,那么無(wú)論如何寫(xiě)個(gè)測(cè)試類(lèi)吧,在寫(xiě)上必要的to do list之后,我開(kāi)始想假如現(xiàn)在代碼寫(xiě)完了,我要怎樣來(lái)使用它呢。哦,對(duì)了,測(cè)試這個(gè)之前還要寫(xiě)個(gè)測(cè)試使用的監(jiān)聽(tīng)器實(shí)現(xiàn),這個(gè)實(shí)現(xiàn)可以很簡(jiǎn)單,把解析結(jié)果Document干掉好了,呵呵,驗(yàn)證結(jié)果還更輕易,那就把它所有節(jié)點(diǎn)remove掉好了,^_^。(注:這里的測(cè)試還沒(méi)有到主功能,只是先做測(cè)試監(jiān)聽(tīng)器部分)不過(guò)這個(gè)時(shí)候,我忽然覺(jué)得目前這個(gè)設(shè)計(jì)似乎還是有些麻煩(懶惰是程序員的通病,sweat,每次想偷懶都想起這句),要寫(xiě)監(jiān)聽(tīng)器,還要讓dataBuilder處理監(jiān)聽(tīng)列表觸發(fā)事件,雖然讓過(guò)濾數(shù)據(jù)操作可以很獨(dú)立地添加,而監(jiān)聽(tīng)器的注冊(cè)也可以通過(guò)XML配置文件來(lái)完成,但還是顯得多余,似乎目前這個(gè)需求只要一個(gè)Decorator模式,寫(xiě)一個(gè)修飾類(lèi)來(lái)修飾ResultSet解析器就差不多了,以后需要新的功能,換Decorator就可以了,也可以相互嵌套來(lái)完成多個(gè)功能,這樣的話,就不需要對(duì)dataBuilder動(dòng)太多手腳了。sweat,還好自己沒(méi)動(dòng)手瞎忙(無(wú)論如何,測(cè)試優(yōu)先讓我重新熟悉自己的設(shè)計(jì),以及目標(biāo),于是我義無(wú)反顧地拋棄了原來(lái)的想法)。新目標(biāo)出現(xiàn)了,看來(lái)我需要一個(gè)解析器接口實(shí)現(xiàn)的Decorator類(lèi)(注:這個(gè)ResultSet解析器類(lèi)原本就是一個(gè)接口ResultSetParser的實(shí)現(xiàn)),我可以先寫(xiě)一個(gè)擴(kuò)展解析器接口的抽象類(lèi)來(lái)包裝下,以后的Decorator實(shí)現(xiàn)都從這個(gè)抽象類(lèi)繼續(xù),直接實(shí)現(xiàn)修飾內(nèi)容就可以了(實(shí)際我一直認(rèn)為,在Decorator模式中所有Decorator實(shí)現(xiàn)類(lèi)都有一個(gè)父抽象類(lèi)繼續(xù)自修飾目標(biāo)類(lèi)的接口,其最主要的目的是使Decorator實(shí)現(xiàn)類(lèi)功能更清楚,因?yàn)閷?shí)際這個(gè)抽象類(lèi)要包裝的東西其實(shí)很少,這些移到子類(lèi)中也完全可以,這樣的話子類(lèi)就是直接實(shí)現(xiàn)修飾目標(biāo)類(lèi)的接口了,效果一樣,所以我認(rèn)為這里有一個(gè)這樣的抽象類(lèi)統(tǒng)一由所有修飾類(lèi)繼續(xù),更主要的是公布,一個(gè)修飾目標(biāo)類(lèi)接口的實(shí)現(xiàn)類(lèi)它是一個(gè)修飾類(lèi),這在閱讀程序,以及使用該API上可以達(dá)到很好的效果。)。OK,就這么辦(我總是很輕易下決定呢,^_^)。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注