最近,身邊有不少的團隊或個人開發者,都開始採用所謂「測試驅動式開發(Test-Driven Development,或簡稱為TDD)」的方法,有些團隊或朋友,甚至採用了好一陣子,也得到不錯的成效。最近在網路上關於「測試驅動式開發」這個主題有一些討論,可以說是相當精采。不過,究竟什麼是「測試驅動式開發」呢?

什麼是測試先行?
目前採用「測試驅動式開發」的團隊,大多是採用敏捷開發(Agile Development)的團隊。這並不是說「測試驅動式開發」僅能搭配敏捷開發來運用,而是說,敏捷開發相對而言,更能從「測試驅動式開發」得到好處。事實上,「測試驅動式開發」的原始觀念,便是發源自極限編程(eXtreme Programming)這一個敏捷開發方法中的實踐守則──測試先行。

所謂「測試先行」,其實它的意義並不是先做測試,而是先寫好測試的程式。也就是說,當程式設計者收到需求和規格打算著手開始撰寫實作的程式碼前,先把用以測試這段程式碼的單元測試程式給寫好。這麼一來,等實作的程式碼完成之後,立即就有可以測試這段實作程式碼的單元測試程式了。

對敏捷開發方法略有了解的朋友,相信都能明白,自動化的單元測試對敏捷開發究竟有多麼的重要。有了值得依靠的自動化單元測試,才能夠禁得起在需求頻繁變更下所引發的程式碼更動。因為任何的程式碼更動,都有可能引起不可預期的副作用,也都需要重新測試。

有了單元測試程式,能更有信心地確保所做的更動,沒有造成我們不希望看到的負面效應。現在很多朋友都會利用重構的技巧來改善程式碼裡的問題,而這種更動同樣有可能引起預期以外的效果,為了確保重構後不會衍生出額外的問題,同樣需要足夠的單元測試程式來做為支持。

像XP這樣的敏捷開發方法,之所以會被發展出來,多半是因為要處理需求頻繁變更的軟體開發現實面貌,而由於這個考量,程式碼也必須跟隨著需求持續演化。

我們可以猜想,在發展這一套方法的過程中,因為觀察到程式碼持續變動所產生的各種負面效應,發現了必須倚靠單元測試程式、甚至是自動化單元測試程式的必要性。因為,缺乏了這項元素,持續變動程式碼就會變成一個充滿高風險的活動。有了足夠質與量的單元測試程式之後,程式設計者對於持續變動程式碼才能放心的去進行──不論是基於面對變更的需求,或者是因為重構的理由。由此可以歸納出,單元測試程式對於此類敏捷開發方法的重要性。

為什麼單元測試程式要先開發?
不過問題來了,為什麼在XP裡要安排先完成單元測試程式,才開始撰寫實作的程式碼呢?雖然單元程式很重要,但是為什麼不能等到實作程式碼完成後,才開始撰寫單元測試程式呢?只要都有對應的單元測試程式就好了,不是嗎?

如果這麼來看待,那麼就只看到了單元測試程式的重要性,而忽略了在撰寫程式之前先完成單元測試程式,除了測試之外的其他作用。

事實上,也有很多人誤以為「測試驅動式開發」的重心是在測試,而忽略了:其實無論如何,你都是需要做測試的,只不過這個開發方法希望你先寫測試程式,進而透過撰寫測試程式的動作,來驅動整個開發流程。在任何開發方法中,測試都很重要,但是在「測試驅動式開發」中,它是扮演一個發動機的角色,由它來發動開發流程當中的每一個階段。

測試先行在開發上有一些重要的意義,而其中協助程式設計者轉換觀點,就是一個很重要的意義。

一般而言,程式設計者的主要工作就是完成功能實作的程式碼。所以,當程式設計者在進行設計或是撰寫程式碼時,他會很容易把觀點放在如何正確、完整地把功能給實作出來。從這個觀點來出發,程式設計者很容易會傾向於從正面來思考程式的實作,而這樣子的傾向,很容易造成程式設計者只考慮到程式「怎麼做會對」,而忽略了程式「可能會怎麼錯」。

這是一些程式設計者的通病,只考慮到程式在正常情況的執行路徑,而忽略了各種可能的異常情況,在他們的眼中只有「晴天」,而沒有「雨天」。所以他們設計出來的程式,在大多數正常的情況下都能運行,一旦遇到了程式中沒有考慮到的「雨天」情境時,程式就無法正確地作用。

當程式設計者在撰寫實作程式碼前先撰寫測試程式時,他會好像搖身一變,成為一名測試者。他看待實作程式碼的觀點,就會從防守者的角度切換到攻擊者的角度。他會不斷思考,這實作的程式碼會在那些地方出錯,接著在測試程式中提出考驗。

這個角色的變換,有助於程式設計者跳出實作者的思路盲點。當你關心的不再只是「實作出來」,而是試著預測那邊可能會實作不完整,或者是實作錯誤──這些考量都將會納入到測試程式之中。接著,當程式設計者開始撰寫這些未來即將受測的實作程式碼時,這些在撰寫測試程式時的想法及考量,便可以起回饋的作用,使得實作的程式碼足以因應測試程式的挑戰,最後自然更容易得到更穩固、更完備的程式碼。

這當然不是說,非得撰寫測試程式碼才能做到這件事,而是說,當我們撰寫測試程式時,因為角色變換的關係,更容易全心全意專注在攻擊之上。

角色變換的作用,除了讓程式設計者更容易進行全面的考量之外,同時也能協助設計者切換角色、從使用者的觀點來看待自己所做的設計。

當程式設計者撰寫測試程式時,他就從設計者的觀點切換到使用者的觀點。對於即將要實作的待測程式碼,他所扮演的其實是客戶端程式設計者(client programmer)的角色。身為設計者多少會有設計者的盲點,而這盲點所無法顧及的,便是客戶端程式設計者在使用時的體驗及感受。

做為一名單純的設計者,在沒有寫下真正應用自己程式碼的客戶端程式碼時,有時會太過於片面的想像可能的應用情境。而這應用情境就真的很有可能是純粹靠想像所堆砌出來。可是,當設計者在撰寫測試程式碼的時候,頓時他就成了客戶端程式碼。因為在撰寫測試程式時,會實際經歷各種可能的應用情境,而這些應用情境的實際運用,就足以讓設計者實際體會他所做的設計在各種情境下被運用的真實情況。

角色變換的意義
當設計者做好了介面的設計之後,便可以開始著手撰寫測試程式。而在測試程式的實際體會過程中,很有可能發生的情況是,他發現他所做的設計在實際運用上有所不足或者是不恰當處。因此,他可以回過頭去重新修改他所做的介面設計,直到在測試程式的撰寫中覺得一切妥當。

雖然這時僅僅只完成了介面的設計,實作的程式碼尚未寫下任何一行,但是透過測試程式的撰寫,已經可以讓設計者體會未來客戶端程式設計者可能遭遇到的各種可能應用情境,進而回饋到設計本身。這則是在撰寫測試程式時,由於角色必須變換所能帶來的另一個價值。

從表面上來看,撰寫測試程式是為了測試而做準備,但是,除了測試之外,對於設計、對於實作,撰寫測試程式都能提供額外且同樣重要的作用。這是測試之所以要先行的重要原因。忽略掉這些原因來思考「測試驅動式開發」,便會有所欠缺。

專欄作者


熱門新聞

Advertisement