引子
單元測試在開發人員中的普及,障礙很多。單測技術除外,單測意識問題、對單測收益的疑惑、做起來以后怎么持續等等,打擊著開發人員開始單元測試的決心。寫這篇文章,也真是為了消除大家對單測的疑惑,提高對單測的認識。認識的飛躍讓你輕松面對單測,不把單元測試當負擔,提升代碼質量的同時,提升自己的設計能力,甚至嘗試開發模式的變革,愛上單元測試并愛上開發。
一些記憶猶新的回憶
十多年前,作為開發人員進入了某大公司,有幸還是不幸進入了CMM2到CMM4的試點項目組。“V”模型,coding完后UT。清晰記得胖胖的女 QA,要求100%的行覆蓋率,85%以上的C/D覆蓋率。加班到11點,因覆蓋率而失眠,是否發現很多bug,已經不記得?;仡^想想,當時的目標是明確的,各種覆蓋率,完美的單元測試報告,可以順利的進入集成測試階段。
后來轉戰了一些公司,開發人員做單元測試依然是不成文的規矩,沒有那么嚴格,有測試報告就好。終于有一天,我成為了“擎天柱”。
開發關聯引擎,自鳴得意于架構、各種設計模式。很多關聯關系沒有考慮清楚,沒有做全面的單元測試,悲劇了!同樣悲劇的王五,做的是客戶端。接下來的日子就是加班改bug,為了沒有考慮到的關聯關系增加特殊處理代碼。“精美”的設計不再存在,糟糕的代碼質量是第一映像。痛苦的經歷,人也正是經歷了痛苦才長大。
工作多年開發了很多C/S架構的系統,系統測試階段問題定位最為痛苦。Client端的開發人員先排除問題,而后再轉到Server端的開發人員排除。如果前后兩端都有問題,也許要改多次才能通過。項目結束后的bug分析總結,50%以上都寫著bug產生原因:單元測試不充分。問題定位成本的 1:10:100,說明了問題,系統測試階段的問題定位成本是最高的。
……
故事還有很多,記住了這些,對單元測試的意識還只是初級階段。意識到了:
單測能降低bug發現成本;
單測能提高提測質量;
單測粒度可以用各種覆蓋率來衡量;
沒有技術含量的配置管理
年輕難免心高氣傲,進入知名企業,都期待的干有挑戰性,前沿的技術活。你在最求卓越、最求新技術的時候,是否忽略了一些不起眼的小事呢?你的開發機上是否還有別人不知道的自測腳本?你是否每日提交自己都不確認正確的代碼?你是否先開始寫代碼再邊寫邊想目錄結構?如果你的回答都是“是”,恭喜你,你還是不成熟的小碼工。
大家很幸運,沒有經歷過產品代碼只有在某幾個人電腦中有情況。來到百度就知道SCM,就知道SVN,但是對配置管理還是認識不夠。邊寫代碼邊考慮目錄結構,就意味著會忽略測試腳本的存放目錄;測試機和本地還有別人不知道的自測腳本,意味著沒有做到測試case與代碼同源,沒有真正意識上的自動化。想起以前開發團隊中的兩個同學,提測質量都很高,都做單元測試。不同的是,A同學使用xUnit寫測試case并提交配置管理,B同學是用main函數來做單元測試且不提交配置管理。B同學平時測試時重新main,或到本地找測試case的成本自己是可以忍受的,終于有一天,系統崩潰了,所有的測試case 都沒了。
開始單元測試前,花時間了解一下“沒有技術含量”的配置管理,能讓你的單測case發揮更大的作用。
開始了單測,但依然是被動接受
也許骨子里就是將開發和測試嚴格分離,很長時間都是先寫完代碼才考慮測試。其實很多開發人員都有我同樣的習慣,成千上萬代碼完成后,再來補單測的心情可想而知了。是不是的會冒出這樣的想法:寫類似接口測試的單測盡快達到覆蓋率吧;就測試幾個重要函數算了;單測做得太完美了,QA做什么?這些想法說明了做單測時的被動,單測的效果無疑會大打折扣。在這種想法的驅動下,“大單測”的概念也會產生,接口測試和單元測試的差異也被模糊,自然不會理解沒什么還需要QA?為什么要用stub或mock?
單測能幫助你盡快驗證提交的代碼。每日為自己生產的代碼增加單測case,使用stub或mock技術隔離依賴,代碼提交前在本地執行單測 case(local build),這些好的習慣無疑能消除你補測試case的苦悶,主動的單測。每日的一小步,讓你代碼質量更高,睡得安穩。
代碼需要呵護,持續重構
單測的意識在不斷的提高,然而新的苦悶又來了。成長的代價,讓我留下了太多沒有單測的代碼,它們依然在使用、在升級,下定決心來補充單測吧。“這代碼怎么寫得這么爛?”“這個函數我沒法寫出單測case了”,自己給自己挖了很多坑。這時,是放棄單測還是重構后繼續單測?從短期成本來看,應當放棄;但長期來看,必須重構。每一個開發人員都希望大家認可自己的代碼,希望有寫代碼的成就感:功能好用、性能優良、代碼很美。不可測的方法、代碼中的壞味道需要找時間將它們消除掉,唯有持續重構:在修改bug時順便重構;在增加新功能時重構;利用CR的機會重構。為了那一份榮耀:讀你的代碼如欣賞優美的藝術品,這些工作都值。
到處,我已經不再僅僅考慮軟件的外部質量,而會同時關注內部質量,可測性、潔凈代碼原則和方法,我都會考慮,開發活動因此而更加豐滿。
不再只是小碼工,也是架構師
經歷了將模塊全面推翻重構的苦痛(嚴格說是重寫了),我們會開始思考,為什么不能將思考提前,為什么不能未雨綢繆呢?現在我們還是完整的詳細設計,開發,再測試,不過我們每天都為提交的代碼寫測試case,及時的發現代碼可測性問題。測試和開發依然界線分明,為了實現設計而編碼,為了驗證編碼而測試。能不能將測試case提前?能不能讓測試來指導設計?TDD終于出現了。我是08年接觸到TDD,不過現在它依然很“新”,沒有被廣泛的實踐,它將帶來開發模式的變革。
用一句話描述TDD:寫代碼是為了修復失敗的測試。它是一種分析技術、設計技術。
測試先行,開始編碼前你已經想到了怎么去測試、準備好了測試case,代碼可測性問題提前考慮,依賴倒置原則(DIP)、開閉原則(OCP)、單一職責原則(SRP)都會用來指導你的設計,這樣的代碼寫起來感覺很好。你又向前邁進了一步,不再只是小碼工,也是架構師了。