不同的人對(duì)單元測(cè)試的理解可能不同,本文中提到的單元測(cè)試是指同時(shí)滿足下面兩個(gè)條件的測(cè)試:
- 運(yùn)行時(shí)間在0.1秒之內(nèi)。(因?yàn)槿绻粋€(gè)測(cè)試訪問了數(shù)據(jù)庫(kù),文件系統(tǒng)或其它外部依賴,運(yùn)行時(shí)間肯定超過0.1秒,可以參考《Working Effectively with Legacy Code》)
- 能自動(dòng)驗(yàn)證產(chǎn)品代碼的正確性。
我們從幾年前開始推行單元測(cè)試,但是一直做得不好,每當(dāng)想到單元測(cè)試,我就想到一次回顧會(huì)議上的一段對(duì)話,
小S:“這個(gè)任務(wù)估計(jì)2天完成。”
小A:“我們從這個(gè)Sprint開始鼓勵(lì)寫單元測(cè)試?!?br> 小S:“等一等,寫單元測(cè)試還需要1天,這個(gè)任務(wù)估算應(yīng)該是3天?!?br> 小A:“好吧。。?!?/p>
很多程序員的想法和小S是一樣的,不愿意寫單元測(cè)試是因?yàn)閾?dān)心寫單元測(cè)試要花費(fèi)額外的時(shí)間,影響任務(wù)的按時(shí)完成。我們組織過《xUnit Test Patterns》的學(xué)習(xí),也做了不少關(guān)于單元測(cè)試的分享,大家雖然都認(rèn)同單元測(cè)試的重要性,但是回到項(xiàng)目組里面以后往往因?yàn)?strong>交付的壓力就把單元測(cè)試擱在一邊了,所以一直沒能養(yǎng)成寫單元測(cè)試的習(xí)慣。我們也嘗試過TDD,但是感覺更難。相信很多公司和我們類似,Time and time again我問自己“到底怎么才能讓大家都寫單元測(cè)試呢?”
最近剛剛讀完《習(xí)慣的力量》,這本書給了我很大的啟發(fā)(有了錘子,什么都看上去像釘子?)。一個(gè)習(xí)慣有三部分組成:暗示,慣常行為與獎(jiǎng)勵(lì),我首先想到了獎(jiǎng)勵(lì),如果沒嘗到單元測(cè)試的甜頭,為什么要寫單元測(cè)試呢?那寫單元測(cè)試的甜頭和獎(jiǎng)勵(lì)是什么?
最近我們的生產(chǎn)環(huán)境連續(xù)兩次出了一個(gè)相同的問題,一位同事為了讓本地的集成測(cè)試通過,把一行產(chǎn)品代碼注釋掉了,測(cè)試完成后忘記把代碼復(fù)原了,代碼示意如下,
//下面代碼是經(jīng)過加工簡(jiǎn)化后的產(chǎn)品代碼
Cost updatedCost = retrigerCost(order);
String costMessage = generateMessage(updatedCost);
//sendMessage(costMessage); //這行代碼被注釋掉了,因?yàn)楸镜貨]有配置消息服務(wù)器
經(jīng)常寫單元測(cè)試的人能一眼看出注釋掉的代碼可以變成一個(gè)“Seam”,我們?cè)诓桓淖儺a(chǎn)品代碼的情況下可以通過改動(dòng)“Enabling Point”替換代碼的行為,不讓它往消息服務(wù)器上發(fā)消息。(可以參考《Working Effectively with Legacy Code》)對(duì)于這個(gè)例子來說,寫單元測(cè)試的獎(jiǎng)勵(lì)就是不用注釋產(chǎn)品代碼就能跑通測(cè)試,省去了檢查,修復(fù)和部署生產(chǎn)環(huán)境的時(shí)間。
去年年初的時(shí)候,香港和珠海團(tuán)隊(duì)移交了幾個(gè)項(xiàng)目給我們,因?yàn)榇蠹覍?duì)代碼不熟,代碼又沒有單元測(cè)試(典型的遺留代碼),每次改代碼的時(shí)候都有如履薄冰的感覺,代碼上線以后經(jīng)常會(huì)出現(xiàn)已有功能被改壞了的情況,現(xiàn)在大家就怕在遺留代碼上做改動(dòng)。由此我想到的第二個(gè)獎(jiǎng)勵(lì)是用單元測(cè)試的思維去改遺留代碼會(huì)更安全。
仔細(xì)想想,單元測(cè)試的獎(jiǎng)勵(lì)還有很多,比如我們現(xiàn)在基于UI的自動(dòng)化測(cè)試只能覆蓋最基本的流程,但是單元測(cè)試能以更低的成本覆蓋更多的邏輯與數(shù)據(jù)多樣性。再比如很多同事反映把代碼部署到本地的應(yīng)用服務(wù)器很慢,寫單元測(cè)試能有效地降低部署的次數(shù),節(jié)省部署的時(shí)間。
想完獎(jiǎng)勵(lì),接著想暗示,那么寫單元測(cè)試的暗示或者開關(guān)是什么呢?很自然的就是:
每當(dāng)我們想注釋產(chǎn)品代碼的時(shí)候
每當(dāng)我們想安全地改動(dòng)遺留代碼的時(shí)候
每當(dāng)我們想自動(dòng)化測(cè)試復(fù)雜業(yè)務(wù)邏輯的時(shí)候
每當(dāng)我們想節(jié)省環(huán)境配置與部署時(shí)間的時(shí)候
。。。
有了獎(jiǎng)勵(lì)和暗示還不夠,要讓一個(gè)人做出改變,真正開始養(yǎng)成寫單元測(cè)試的習(xí)慣還是很難的。就像我很早就想養(yǎng)成晨跑的習(xí)慣,但是直到體檢醫(yī)生說我脂肪肝的時(shí)候,我才真的開始晨跑。所以我們還缺一個(gè)類似“脂肪肝”的不大不小的危機(jī)。
Wise executives seek moments of crisis, or create the perception of crisis, and cultivate the sense that something must change.
危機(jī)是獎(jiǎng)勵(lì)的反面,但是我們可以利用不寫單元測(cè)試帶來的危機(jī)(比如每次發(fā)布新版本總會(huì)破壞已有的功能,影響客戶業(yè)務(wù)的正常運(yùn)作,給客戶帶來損失,造成賠償或者客戶流失。),向大家傳遞一種緊迫感,使大家感受到是時(shí)候養(yǎng)成寫單元測(cè)試的習(xí)慣了。
總結(jié)一下,推行單元測(cè)試的第一件事情也是最重要的事情是找到它能帶來什么獎(jiǎng)勵(lì),然后找到寫單元測(cè)試的暗示和開關(guān),盡早地讓大家切身地感受到這些獎(jiǎng)勵(lì),最后利用不寫單元測(cè)試帶來的危機(jī),讓大家立即開始養(yǎng)成寫單元測(cè)試的習(xí)慣,用習(xí)慣思維持續(xù)催化單元測(cè)試,讓單元測(cè)試成為團(tuán)隊(duì)的新常態(tài)。