張大胖和單元測(cè)試
1.敏捷
運(yùn)動(dòng)張大胖的公司正在掀起一場(chǎng)轟轟烈烈的敏捷運(yùn)動(dòng)。
似乎一夜之間, 每個(gè)人的嘴邊都掛起了scrum, xp , tdd,user story 等敏捷詞匯。
公司要求, 每個(gè)開(kāi)發(fā)人員都必須掌握單元測(cè)試這個(gè)非?;镜拿艚輰?shí)踐,為此公司還專(zhuān)門(mén)組織了單元測(cè)試培訓(xùn)。
張大胖自然不能落后, 他熱情滿滿地參與了培訓(xùn), 在會(huì)議上了解了單元測(cè)試的各種好處, 學(xué)會(huì)了JUnit這個(gè)簡(jiǎn)單又強(qiáng)大的測(cè)試工具,理解了一個(gè)測(cè)試用例執(zhí)行之前會(huì)調(diào)用"Setup"方法做必要的初始化, 執(zhí)行完畢以后會(huì)調(diào)用 “teardown”做清理。
培訓(xùn)中還特別提到了如何做Mock對(duì)象, 這讓張大胖印象深刻: 原來(lái)那些不存在的或者難于new 出來(lái)的對(duì)象可以使用Mock工具(EasyMock, jmockit等) 來(lái)“造假”啊!
公司還專(zhuān)門(mén)定義了什么是好的單元測(cè)試:
1. 單元測(cè)試是“白盒測(cè)試”, 應(yīng)該覆蓋各個(gè)分支流程、異常條件
2. 單元測(cè)試面向的是一個(gè)單元("Unit"), 是java中的一個(gè)類(lèi)或者幾個(gè)類(lèi)組成的單元。
3. 單元測(cè)試運(yùn)行一定要快!
4. 單元測(cè)試一定是可重復(fù)執(zhí)行的
5. 單元測(cè)試之間不能有相互依賴,應(yīng)該是獨(dú)立的。
6. 單元測(cè)試代碼和業(yè)務(wù)代碼同等重要, 要一并維護(hù)。
培訓(xùn)結(jié)束了,張大胖信心滿滿: 寫(xiě)單元測(cè)試簡(jiǎn)直就是小菜一碟!
精明的項(xiàng)目經(jīng)理趁熱打鐵、不失時(shí)機(jī)對(duì)大家提出了要求: 兄弟們,我聽(tīng)說(shuō)隔壁組定了一個(gè)目標(biāo), 單元測(cè)試的代碼覆蓋率要達(dá)到70% , 我們一定要超過(guò)他們, 我們的覆蓋率要達(dá)到 75% !
2.困惑
張大胖磨拳擦掌,準(zhǔn)備大干一場(chǎng) , 他打開(kāi)了Eclipse, 開(kāi)始查看自己之前寫(xiě)的代碼,準(zhǔn)備全部加上單元測(cè)試用例, 搞一個(gè)代碼100%全覆蓋, 勇奪覆蓋率冠軍!
可是***個(gè)小模塊就把張大胖給難住了, 你看看這代碼, action調(diào)用service, service調(diào)用 dao, dao里都是sql, 簡(jiǎn)單的增刪改查,這有什么可測(cè)試的?
唉,為了代碼覆蓋率,硬著頭皮寫(xiě)吧,按照分層測(cè)試的原則, 測(cè)試action的時(shí)候把service給mock出來(lái),測(cè)試service的時(shí)候把dao給mock出來(lái)......但張大胖總覺(jué)得不太對(duì)勁,總是覺(jué)得自己是在測(cè)試框架,而不是測(cè)試業(yè)務(wù)代碼。
當(dāng)然,張大胖也遇到了一些有一定業(yè)務(wù)邏輯的模塊,但是這些模塊患有重度依賴癥,依賴10幾個(gè)其他模塊的接口,為了單獨(dú)測(cè)試他們,張大胖廢了九牛二虎之力,做了10多個(gè)mock對(duì)象才把依賴給解除開(kāi)。
但是mock對(duì)象過(guò)多, 協(xié)調(diào)他們進(jìn)入一致的狀態(tài)來(lái)正確執(zhí)行測(cè)試十分困難: 當(dāng)接口1處于A狀態(tài),并且接口2處于B狀態(tài), 并且接口3處于C狀態(tài)..... 接口10處于X狀態(tài)時(shí), 測(cè)試用來(lái)才能正確執(zhí)行, 唉,真是不容易啊。
一天下來(lái), 這個(gè)mock就把張大胖弄的頭暈。
大胖感慨的想: 敏捷教練們大談單元測(cè)試的好處, 可用來(lái)展示的都是非常簡(jiǎn)單的例子, 現(xiàn)實(shí)的代碼要復(fù)雜的多啊。
第二天便發(fā)生了狀況, 同組的小李改了業(yè)務(wù)代碼,卻忘記修改單元測(cè)試代碼,導(dǎo)致很多單元測(cè)試失敗,那一大片醒目的紅色讓人觸目驚心。
小李去修改單元測(cè)試,可是怎么都讀不懂大胖的測(cè)試用例,他不滿的說(shuō): ”大胖,我覺(jué)得你的測(cè)試代碼比業(yè)務(wù)代碼都要復(fù)雜啊, 你是怎么寫(xiě)出來(lái)的?“
大胖委屈的說(shuō): “別說(shuō)你了, 看看這么多的mock 對(duì)象, 我自己都頭暈, 這該怎么辦呢?”
小李也沒(méi)轍, 這樣下去, 別說(shuō)業(yè)務(wù)代碼了, 光是維護(hù)單元測(cè)試就把人給累死了。
3.討論
他們倆人去找項(xiàng)目經(jīng)理訴苦, 經(jīng)理說(shuō): 有不少人都在說(shuō)這個(gè)問(wèn)題,我們開(kāi)一個(gè)會(huì)議來(lái)討論下吧!
項(xiàng)目經(jīng)理召集了幾個(gè)經(jīng)驗(yàn)豐富的骨干專(zhuān)門(mén)來(lái)討論這個(gè)問(wèn)題。 他先做了一個(gè)開(kāi)場(chǎng)白:
“我們現(xiàn)在的單元測(cè)試進(jìn)行的如火如荼, 我們組做的還是非常好的, 其他組遇到的像”單元測(cè)試運(yùn)行慢”, “單元測(cè)試不能重復(fù)執(zhí)行, 換一臺(tái)機(jī)器就出錯(cuò)”,"單元測(cè)試互相依賴" 等常見(jiàn)問(wèn)題我們組基本沒(méi)有, 我們遇到的主要問(wèn)題有兩個(gè):
1. 張大胖和小李反映單元測(cè)試代碼過(guò)于復(fù)雜, 難于維護(hù), 張大胖那個(gè)mock了10多個(gè)接口的測(cè)試想必你們也看到了
2. 大家認(rèn)為有些非常簡(jiǎn)單的增刪該查沒(méi)必要去做單元測(cè)試 。
如果單元測(cè)試維護(hù)成本越來(lái)越高, 我擔(dān)心大家會(huì)慢慢的拋棄他們, 大家一塊兒來(lái)想想辦法吧”
老梁說(shuō): “我做單元測(cè)試的時(shí)間比較久了, 我認(rèn)為如果測(cè)試代碼需要很復(fù)雜的Setup 才能開(kāi)始測(cè)試, 那就反映了一個(gè)問(wèn)題:我們的業(yè)務(wù)代碼接口設(shè)計(jì)有問(wèn)題! ”
張大胖佩服的說(shuō): “老梁真厲害,一下子就看出了問(wèn)題的本質(zhì), 我當(dāng)時(shí)只是想著怎么把測(cè)試搞定,沒(méi)想到是業(yè)務(wù)代碼的問(wèn)題”
老蔡也附和說(shuō): “說(shuō)的沒(méi)錯(cuò), 簡(jiǎn)單的單元測(cè)試誰(shuí)不會(huì)啊? 關(guān)鍵還是要處理現(xiàn)實(shí)中的遺留代碼, 我們之前有些模塊的API設(shè)計(jì)確實(shí)是有問(wèn)題, 看來(lái)到了重構(gòu)的時(shí)候, 我們趁著這次東風(fēng)把一些不好的設(shè)計(jì)提升一下, 這樣測(cè)試肯定會(huì)變的簡(jiǎn)單。 大胖, 小李, 重構(gòu)的過(guò)程基本上就是一個(gè)重新設(shè)計(jì)的過(guò)程, 這可是個(gè)學(xué)習(xí)的好機(jī)會(huì)啊”
大胖說(shuō):“ 我也了解過(guò)一些重構(gòu), 正好練習(xí)一下。 ”
大家都表示同意, 只是項(xiàng)目經(jīng)理為難的說(shuō): “重構(gòu)可能會(huì)很費(fèi)時(shí)間, 還有可以引入新的bug , 測(cè)試也要介入, 這樣的話會(huì)不會(huì)影響我們的進(jìn)度啊?”
老梁說(shuō): “這也是沒(méi)辦法的事情 ,如果不重構(gòu), 不要說(shuō)單元測(cè)試了, 就連我們的代碼都可能今天被貼個(gè)補(bǔ)丁, 明天再被貼個(gè)補(bǔ)丁, 慢慢的腐化下去, 越來(lái)越難以維護(hù), ***無(wú)人能懂, 無(wú)人敢改,維護(hù)成本可是天價(jià)了。 ”
大胖說(shuō): “沒(méi)事, 為了學(xué)習(xí) ,我愿意加班來(lái)做”
經(jīng)理贊賞的看著大胖,心說(shuō): "這孩子不錯(cuò),挺上進(jìn)的, 年終考核的時(shí)候得傾斜一下"
“好吧,就這么決定” 經(jīng)理說(shuō),“大胖,相關(guān)的重構(gòu)你來(lái)做, 有問(wèn)題的話請(qǐng)教老梁和老蔡吧”
“那增刪該查到底要不要測(cè)試?”
老梁說(shuō):“我那天仔細(xì)思考了一下,這些代碼沒(méi)有邏輯, 就是一層調(diào)用一層, 我覺(jué)得單元測(cè)試必要性不大”
“如果不測(cè)試,怎么保證正確性呢? 我們的代碼覆蓋率也肯定達(dá)不到75%了” 大胖說(shuō)
“沒(méi)有必要特別追求代碼覆蓋率, 要不這樣” 老蔡說(shuō),“對(duì)于這樣的代碼, 咱們就不要寫(xiě)單元測(cè)試了, 還是通過(guò)自動(dòng)化的功能測(cè)試來(lái)覆蓋得了!”
“嗯,我覺(jué)得這樣可行, 功能測(cè)試可以有開(kāi)發(fā)寫(xiě), 也可以由測(cè)試來(lái)寫(xiě)” 項(xiàng)目經(jīng)理說(shuō)
老梁說(shuō): “同意, 還有一點(diǎn)建議是, 之前我們都是程序員在自己機(jī)器上跑單元測(cè)試, 以后咱們要把運(yùn)行的過(guò)程加入到自動(dòng)化的Build當(dāng)中, 包括單元測(cè)試和功能測(cè)試,作為重要的質(zhì)量保證。”
4.一年以后
經(jīng)過(guò)團(tuán)隊(duì)艱苦的努力, 張大胖的項(xiàng)目組通過(guò)單元測(cè)試和功能測(cè)試編織起來(lái)了一張密密麻麻的安全大網(wǎng),不管是多么微小的變動(dòng), 都有測(cè)試用例做回歸測(cè)試, 現(xiàn)在大家需要改動(dòng)起代碼時(shí)比原來(lái)自信多了。
更重要的是, 關(guān)鍵的核心代碼做了重構(gòu),接口API變的越來(lái)越好,代碼易讀易維護(hù),沒(méi)有了臟代碼的羈絆, 新需求實(shí)現(xiàn)起來(lái)也更加容易。
張大胖感慨的說(shuō): “實(shí)現(xiàn)了自動(dòng)化的單元測(cè)試, 我們確實(shí)變得更敏捷了。”
當(dāng)別人問(wèn)他是怎么做單元測(cè)試的, 張大胖說(shuō): “告訴你吧, 關(guān)鍵就在于如何處理遺留代碼。”
【本文為51CTO專(zhuān)欄作者“劉欣”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過(guò)作者微信公眾號(hào)coderising獲取授權(quán)】