單元測試努释,或者更大一些的自動化測試拷泽,對提高軟件質(zhì)量是有很大幫助的疫鹊。通過一系列預先設計的規(guī)則,就可以覆蓋大量的測試點司致。尤其是對重構(gòu)一類的任務拆吆,確保修改前后系統(tǒng)行為不變很重要,而修改后的回歸測試工作量又極其繁重脂矫,此時單元測試枣耀,或者自動化測試就能體現(xiàn)出無以倫比的效率。
我在2005年學Python不久庭再,就郁悶于自己那點代碼手工測試很麻煩捞奕,恰好那時得知了很多Python工程師有做單元測試的習慣,于是就學習了一下拄轻,果然效果卓群颅围。后來又經(jīng)過數(shù)年整理出自己的一套單元測試的規(guī)范。
我做過的各類Python項目恨搓,代碼總量的50%左右是單元測試院促。經(jīng)過這個級別的單元測試覆蓋,確保了底層函數(shù)基本不會出錯斧抱,這樣高層功能的調(diào)試才更方便常拓。同時也是這個覆蓋程度確保了,被測試工程師發(fā)現(xiàn)bug的可能性已經(jīng)很低了辉浦。
我給自己的單元測試設置了5個級別:
1. Level1:正常流程可用弄抬,即一個函數(shù)在輸入正確的參數(shù)時,會有正確的輸出
2. Level2:異常流程可拋出邏輯異常宪郊,即輸入?yún)?shù)有誤時眉睹,不能拋出系統(tǒng)異常,而是用自己定義的邏輯異常通知上層調(diào)用代碼其錯誤之處
3. Level3:極端情況和邊界數(shù)據(jù)可用废膘,對輸入?yún)?shù)的邊界情況也要單獨測試,確保輸出是正確有效的
4. Level4:所有分支慕蔚、循環(huán)的邏輯走通丐黄,不能有任何流程是測試不到的
5. Level5:輸出數(shù)據(jù)的所有字段驗證,對有復雜數(shù)據(jù)結(jié)構(gòu)的輸出孔飒,確保每個字段都是正確的
如上的單元測試分級是我2007年整理出來的灌闺,后來在我做的各種項目中艰争,一般只做到Level2,重要系統(tǒng)或者底層服務桂对,要做到Level3或Level4甩卓。而很少做到Level5。即便如此蕉斜,就已經(jīng)實現(xiàn)了如上所說的逾柿,很難被測試工程師發(fā)現(xiàn)bug。
除了級別外宅此,測試方法也要區(qū)分不同系統(tǒng)的玩法机错。比如基于WEB的系統(tǒng),就需要確保單元測試里可以模擬發(fā)送請求父腕,這個一般是WEB框架提供支持的弱匪。比如我常用的web.py、Flask璧亮、Django都有支持萧诫。不僅僅可以模擬簡單的請求,還可以模擬POST枝嘶、cookie等帘饶。另外一般建議單獨寫個函數(shù)來模擬登錄過程,這樣系統(tǒng)登錄后行為的測試就不必反復模擬登錄了躬络。
單元測試一大痛苦是構(gòu)造測試數(shù)據(jù)尖奔。我的看法是測試數(shù)據(jù)應該是人造的,而不是隨便從產(chǎn)品環(huán)境dump出來一份穷当。只有人造的數(shù)據(jù)能確保環(huán)境可控提茁,每次運行不會因為環(huán)境改變而頻繁修改testcase。我的常用玩法是測試數(shù)據(jù)分為基礎數(shù)據(jù)和附加數(shù)據(jù)兩部分馁菜≤畋猓基礎數(shù)據(jù)是所有testcase共享的,比如建立幾個常用角色的用戶等等汪疮。附加數(shù)據(jù)是testcase內(nèi)部自己建立的幢泼。這樣每次testcase運行時,先清空數(shù)據(jù)庫歼指,導入基礎數(shù)據(jù)糠悯,導入附加數(shù)據(jù),然后執(zhí)行測試盏道,驗證結(jié)果稍浆。
各類程序的函數(shù)可以分為純函數(shù)和副作用函數(shù)。純函數(shù)對應的是數(shù)學里函數(shù)的概念,輸出和輸入是一一對應的衅枫。對一個輸入有確定的輸出嫁艇。比如1+1=2。而副作用函數(shù)則相反弦撩,同樣的輸入步咪,在不同時間和環(huán)境里,可能有不同的輸出益楼。比如任何涉及IO猾漫、網(wǎng)絡、數(shù)據(jù)庫的偏形。副作用函數(shù)的測試比純函數(shù)麻煩的多静袖,因為你必須要完整的構(gòu)造其所依賴的所有環(huán)境,才能夠復現(xiàn)一個副作用函數(shù)的行為俊扭。也正因為如此队橙,副作用函數(shù)出bug的概率比純函數(shù)高的多。理解這個概念以后萨惑,應該盡可能的把程序里的純函數(shù)和副作用函數(shù)進行拆解捐康,降低副作用函數(shù)的比例和邏輯復雜度。還有庸蔼,副作用函數(shù)是會傳染的解总,一個函數(shù)如果調(diào)用了副作用函數(shù),那么它也會變成副作用函數(shù)姐仅。