綠燈 | 紅燈

妙思資訊

2011年11月20日 星期日

什麼是TDD(Test Driven Development)

TDD(Test Driven Development),中文有人稱為"測試先行"。意思就是,在開發程式時,要以測試的角度來設計。以前若不是使用TDD的方式來設計程式架構,所設計出來的API可能是無法經由測試程式來進行自動測試。如果程式無法被測試,那就是很不好的設計。
舉個例子來說,家裏的電燈不亮,如果在有電的情況下,不是底座壞了,就是燈泡壞了。這時侯你可能先把燈泡拆下,放到沒有問題的底座上,測試看看燈炮是否有問題,如果燈泡亮了,則代表問題不在燈泡(可能是底座或電線等問題),若燈泡不亮,則肯定是燈泡壞了。所以燈泡是可測試的零件;如果有廠商把燈泡和底座封起來,變成一體成型的話,一旦那天電燈不亮,不僅無法個別測試,也無法單獨更換燈泡,每次壞了就要底座和燈泡一起更換,那這種電燈就是不好的設計。從這個例子中,是否有隱約感受到開發程式時常講的Coupling(耦合)和Decoupling(低耦合),現在越來越多的Framework都強調是否好測試,越是POJO和Decoupling的架構,這個Framework就越好測試。以前Struts 的Action 就因綁了底層的API,所以很難測,之後的Struts2就變得可測試,再來Spring 的MVC 更是POJO 和Decoupling的代表作。所有的Framework 演變到如此,就是為了要能夠測試。不能夠測試的Framework終將會被淘汰。
你設計的API是可以測試的嗎?我以計算租金的API為例,以TDD的方式來進行設計。
租金計算的邏輯如下:
1.租金是依地區和坪數做基本計算。
2.如果房客是公司,因公司要扣繳10%的稅金,房東會提高租金。就是將租金再除以0.9。
3. 如果房客是學生,因考量經濟能力,則以8折優待。

我以JAVA為程式語言,以Eclipse 做為開發的IDE,以JUnit 4為單元測試的Framework。

首先建立一個Project 名為 RentDemo,並有2個Source Folder 分別為"src" 和 "test"。(如圖一)









圖一.



要以TDD的方式來開發,所以就先寫測試。我想有人應該會問,租金的API都沒寫那要怎麼測?話不多說,馬上實作。
1.在test 的的rent.demo package 中建一個Class 名為FirstRentTest。(如圖二)
2. 在FirstRentTest Class 中建一個method 名為 testCalRent。(如圖三)












圖二.











圖三.



3. 開始設計。租金是因為有房子供出租而得,所以會先有House 這個物件,而計算租金calRent 這個method,就由House 這個Domain 物件來提供。再來,坪數(ping)和房客(tenant)和地區(region)都可做為House 的屬性。有了以上的概念後,就可以開始寫Code。























圖四.


4. 建立 myHouse 物件,因還沒有定義House這個Class,所以Eclipse 出現錯誤的紅色X號(如圖四)。請用滑鼠左鍵點選紅色X號,即會出現中間這個對話視窗,請點選Create class 'House',這時會出現另一個對話視窗(如圖五),請將Source Folder 更改為"RentDemo/src",package 不變,按下"Finish"後,就會在"src" 的rent.demo package中建立一個House.class。(如圖六)


(圖五.)

(圖六.)

5. 在House.class 中建立坪數(ping)、房客(tenant)和地區(region) 這3個屬性。坪數可能會有小數所以使用BigDecimal 型別,房客使用 Tenant 型別,地區使用Region 型別。因尚未在程式中定義Tenant 和 Region型別,所以Eclipse 出現紅色X號,同樣以上述手法分別建立Tenant 和 Region 型別,最後在House.class 中將各屬性的 getXXX() 和 setXXX() 建立好。 

(圖七.)

(圖八.)

6. 都建立好後,再回到 FirstRentTest,繼續完成測試碼。將各屬性值設給House 物件,而且House  物件要提供一個計算租金的方法(calRent())。同樣的,因House 物件中尚未定義calRent()這個方法,所以Eclipse 又出現紅色X號(如圖九)。將滑鼠左鍵點選紅色X號,再點選"create method calRent()in type House",便會在House.class 中建立calRent()的方法。

(圖九.)

7.開始假設情節,如果房子有35坪,且座落在大安區,大安區的每坪租金假設為1200元,而房客的身份為一般大眾,則租金=35*12000=42,000元。所以可以為這個情節開始進行測試,啟動測試後,出現紅燈,表示測試未通過,這是正常的,因為我們還未在calRent()撰寫任何程式碼,如果這時測試出現綠燈,那就奇怪了,一定那裡出了問題。(如圖十)

(圖十.)

8. 接下來,只要我們能將測試的紅燈轉變成綠燈就代表我們將calRent()這個method完成,並且通過測試。在計算租金時,我希望由Region物件提供該地區的每坪租金價格,所以Region 就會有2個屬性,分別是name(地區名稱)和 price(每坪租金價格),因為"地區名稱"和"每坪租金價格"是較不易變動的屬性,所以我想以Constructor的方式來將屬性值設給Region 物件。

(圖十一.)

9.再來換房客(Tenant),因房客有三種身份(公司,學生,一般),所以房客會有一個kind(類別)屬性,型別為Enum。再來,房客可以提供各種身份的係數值,以便提供租金計算。例如: 公司為(10/9),學生為(0.8),一般為(1.0)。也是以Constructor的方式將身份屬性設給房客。







(圖十二.)


(圖十三.)

10. 再將測試修正(如圖十四)


(圖十四.)

15. 開始撰寫 calRent()。租金=坪數*每坪租金價格*房客係數,因calRent()的回傳值為double,所以用BigDecimal 來做乘法運算,才不會有精確度不準的問題。
(圖十五.)
16. 回到FirstRentTest 再測試,看是否變成綠燈,如果是綠燈則代表在房客為"一般"的情況下,程式計算正確沒有問題。最後再把房客為公司和學生的情況也進行測試,若都是綠燈,則代表租金計算的API已完成,且通過各種情境測試。


(圖十六.)

最後,以TDD的方式開發還要看IDE是否有支援,有些IDE就不能從Pseudo Code中建立Class 或Method。另外使用敏捷(Agile)開發流程如XP(Extreme Programming)或Scrum 是比較有機會使用TDD的開發方式。如果是使用傳統的瀑布式的開發流程,先等文件寫好再開發,那麼設計就不容易變更,TDD的開發也較難落實。

如果沒有使用TDD的方式來開發calRent(),那麼程式碼會是什麼樣呢?可能會是一堆if..else 塞滿整個程式,如果有新的房客身份,又要加一個if判斷。因為思考的方試不同,所以寫的方式也就不同。使用TDD,會以好測試的角度來設計程式,所以程式就會設計的較乾淨,大家可以嘗試寫寫看。

沒有留言: