什么TDD,讓它見(jiàn)鬼去吧!
張大胖是個(gè)積極進(jìn)取的程序員, 在日常工作之余,他還學(xué)習(xí)單元測(cè)試,重構(gòu)等編程實(shí)踐。
這一天晚上他看到微信群里在激烈地爭(zhēng)論一個(gè)叫TDD的東西,不由地來(lái)了興致,上網(wǎng)搜索了一下。
原來(lái)TDD就是Test Driven Development(測(cè)試驅(qū)動(dòng)開(kāi)發(fā)),強(qiáng)調(diào)測(cè)試先行,小步快跑,用測(cè)試用例驅(qū)動(dòng)出程序的接口和代碼。
1
TDD步驟看起來(lái)異常簡(jiǎn)單:
1. 寫(xiě)一個(gè)失敗的測(cè)試用例
2. 寫(xiě)一點(diǎn)代碼,讓這個(gè)測(cè)試通過(guò)
3. 重構(gòu)代碼(如果需要的話),轉(zhuǎn)到第一步
張大胖心想,這三個(gè)步驟不就是“把大象關(guān)到冰箱里”嘛,太抽象了! 一點(diǎn)兒都不實(shí)用!
他又搜了一些文章,發(fā)現(xiàn)這些文章中講的都是一些極其簡(jiǎn)單的例子,如加減法計(jì)算器,貨幣轉(zhuǎn)換等等。
比如這個(gè)計(jì)算器的例子,第一步先寫(xiě)一個(gè)簡(jiǎn)單的測(cè)試用例,用來(lái)測(cè)試兩個(gè)數(shù)字相加的行為。
- public class CalculatorTest {
- @Test
- public void testAdd(){
- Calculator calculator = new Calculator();
- int result = calculator.add(10,20);
- Assert.assertEquals(30, result);
- }
- }
第二步在Calculator中實(shí)現(xiàn)add方法,完成兩個(gè)數(shù)相加的邏輯,讓測(cè)試通過(guò)。
- public class Calculator{
- public int add(int a, int b){
- return a + b;
- }
- }
這個(gè)邏輯極其簡(jiǎn)單,就不用重構(gòu)了。直接寫(xiě)下一個(gè)測(cè)試用例, 測(cè)試兩個(gè)數(shù)字相減的行為。這樣周而復(fù)始下去,直到所有功能都完成。
張大胖撇撇嘴:這就是TDD? 太沒(méi)技術(shù)含量了,我明天就在項(xiàng)目中嘗試一把!
2
第二天,張大胖看了一下自己的任務(wù)列表,里邊有這么一個(gè)需求:
在下訂單的時(shí)候,根據(jù)訂單的金額,扣除優(yōu)惠券,按照規(guī)則給用戶增加相應(yīng)積分
張大胖看了看這個(gè)計(jì)算規(guī)則,非常簡(jiǎn)單,估計(jì)一個(gè)函數(shù)就能搞定。
好,就拿你來(lái)試一試TDD這把刀吧,看看TDD到底有沒(méi)有那么好,或者那么差。
第一步,先寫(xiě)一個(gè)失敗的測(cè)試!
張大胖心中非常清楚,這個(gè)系統(tǒng)用的是Spring,典型的Controller -> Service -> DAO。
在Controller中根本沒(méi)有邏輯,就是調(diào)用Service而已。所以直接對(duì)Service層寫(xiě)單元測(cè)試吧, 張大胖很快就定位到這個(gè)新需求相關(guān)的類, 即OrderService的submit方法。
TDD本來(lái)是要驅(qū)動(dòng)出接口的,現(xiàn)在看來(lái)不用了,已經(jīng)存在了,張大胖看了一下接口的輸入輸出:
- public class OrderService{
- public String submit(String requestBody){
- ......
- }
- }
這個(gè)方法的輸入?yún)?shù)居然是一個(gè)XML字符串! 其中包含了像couponID, addressID這樣的東西。
- <createOrder>
- .....
- <addressID>xxxx</addressID>
- <couponID>xxxx</couponID>
- <paymentType>xxxx</paymentType>
- ......
- </createOrder>
返回值也是一個(gè)XML字符串, 表示成功或者失敗(以及對(duì)應(yīng)的失敗消息)。
- <result>
- <status>xxxx</status>
- <msg>xxxx</msg>
- </result>
這年頭還用XML做參數(shù),只能說(shuō)這是一個(gè)老應(yīng)用了!
3
按照TDD的節(jié)奏, 張大胖寫(xiě)下第一個(gè)測(cè)試用例,并且讓它失敗。
- public void OrderServiceTest{
- public void testBonusPoints(){
- String requestBody= ......;
- //執(zhí)行submit方法
- String result = orderService.submit( requestBody);
- ?? 驗(yàn)證積分, 可是怎么驗(yàn)證??
- }
- }
等一下,這個(gè)測(cè)試的輸入?yún)?shù)容易構(gòu)建,但是submit方法的返回值中根本就不會(huì)包含積分信息!那怎么才能我計(jì)算出的積分是正確的?
難道讓submit方法返回積分?jǐn)?shù)據(jù)?那就修改了本來(lái)是通用的接口協(xié)議,太不像話了!
第二個(gè)問(wèn)題也很快浮現(xiàn),積分計(jì)算的邏輯很簡(jiǎn)單,但是需要訂單總金額和優(yōu)惠券這兩個(gè)信息,可是在測(cè)試用例中,這兩個(gè)信息從哪里來(lái)?
訂單總金額需要購(gòu)物車,這是在數(shù)據(jù)庫(kù)存放的,優(yōu)惠券ID在submit方法的參數(shù)中,詳情也在數(shù)據(jù)庫(kù)中。
積分的計(jì)算這么簡(jiǎn)單,難道我還得先在數(shù)據(jù)庫(kù)中創(chuàng)建一個(gè)購(gòu)物車和優(yōu)惠券,然后通過(guò)ShopCartService和CouponService從數(shù)據(jù)庫(kù)讀出來(lái)?這也太變態(tài)了吧?
不,單元測(cè)試一定要避開(kāi)數(shù)據(jù)庫(kù),必須得用Mock的方式吧,張大胖知道一個(gè)Mock框架叫Mockito,挺好用的,就用它了。
張大胖又瀏覽了一下OrderService.submit這個(gè)長(zhǎng)達(dá)2000多行的函數(shù),這一看不打緊,張大胖發(fā)現(xiàn)這個(gè)函數(shù)依賴了另外七八個(gè)Service: UserService, ShopCartService, CouponService ......
這幾個(gè)Service有的嚴(yán)重依賴數(shù)據(jù)庫(kù), 有的嚴(yán)重依賴Http ,有的依賴消息隊(duì)列。
也就是說(shuō)要想讓submit方法順利執(zhí)行,必須得把這七八個(gè)Service都Mock出來(lái),讓它們能協(xié)調(diào)工作,例如:
給一個(gè)userID,就能返回一個(gè)正確的user對(duì)象。
給一個(gè)couponID,就能返回一個(gè)正確的coupon對(duì)象。
Mockito能實(shí)現(xiàn)這個(gè)功能,但是協(xié)調(diào)七八個(gè)個(gè)Service的相關(guān)對(duì)象,需要寫(xiě)出大量代碼才行!測(cè)試用例中的代碼會(huì)變得非常復(fù)雜、非常脆弱。
張大胖傻眼了 !自己連一個(gè)測(cè)試用例都寫(xiě)不出來(lái),還搞什么TDD?
4
張大胖嘆了一口氣, 放棄了寫(xiě)測(cè)試用例的想法, 在OrderService.submit方法中,找到了合適的地方,然后根據(jù)訂單金額和優(yōu)惠券信息,寫(xiě)了幾十行代碼,把積分計(jì)算了出來(lái),保存到數(shù)據(jù)庫(kù)中。
然后啟動(dòng)程序,通過(guò)界面的方式提交了幾個(gè)訂單,涵蓋了各種情況, 做了手工測(cè)試,然后檢查數(shù)據(jù)庫(kù),他高興地發(fā)現(xiàn),積分計(jì)算完全正確,這才花了不到一個(gè)小時(shí)。
什么TDD, 讓它見(jiàn)鬼去吧!
后記:
實(shí)際上真正的TDD并不是文章中那么簡(jiǎn)單的三個(gè)步驟, TDD正確的做法是根據(jù)需求先寫(xiě)粗粒度的功能測(cè)試,這些測(cè)試能驅(qū)動(dòng)出程序的接口, 然后寫(xiě)細(xì)粒度的單元測(cè)試,驅(qū)動(dòng)出細(xì)節(jié)代碼。今天這篇文章實(shí)在太長(zhǎng)了,就不展開(kāi)了,再寫(xiě)一篇文章來(lái)講吧。
理解了TDD的思路以后,改變思維,實(shí)施TDD并不是一件特別難的事情。
我這些年遇到的主要困難是遺留項(xiàng)目,代碼很亂,可測(cè)試性很差,想寫(xiě)出清晰良好的測(cè)試,經(jīng)常需要重構(gòu)大量代碼,這就得不償失了,說(shuō)是做TDD,其實(shí)大量的時(shí)間是在重構(gòu)代碼,開(kāi)發(fā)進(jìn)度緩慢,看不到立竿見(jiàn)影的好處,于是就放棄了。
【本文為51CTO專欄作者“劉欣”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過(guò)作者微信公眾號(hào)coderising獲取授權(quán)】