寫測試代碼這種事情 ,以前隻在網上和書上看到過, 自己從來沒有寫過。 每當看到那些世界頂級程式員編寫的技術書籍中出現“測試用例”“測試代碼”的字樣或者一些行業的鼎鼎大名的技術大牛們提及寫測試的重要性的時候,我的心裡就會産生一種自己編的一定是假程的錯覺, 為什麼我寫代碼就從來不用那玩意?
就拿開發一個MVC架構的Web應用程式設來說, 通常的做法就是建立一個控制器和一個模型, 把代碼要實作功能的業務邏輯寫在模型裡面,控制器調用模型, 假如有外部參數則接收參數傳遞給模型, 假如業務邏輯過于複雜導緻模型過于臃腫或邏輯不順暢, 則再進行梳理或提取,建構成一個新類,再由模型進行調用。 這一過程反複循環疊代, 直到功能開發完畢。 調試或者測試寫的代碼是否能得出想要的結果, 自然也是使用最簡單粗暴的方法, 在浏覽器中運作程式, 定位到控制器, 控制器調用模型, 模型再調用其它所涉及到的類,拿到結果後再一步步傳回, 浏覽器是否顯示預期結果就意味着我們寫的程式是否正确。不管是我們要測試的功能子產品離控制器隻有一個調用還是有十個調用,都遵循着這樣一個步驟, 因為這是最符合我們直覺和習慣的方式。 我也一直以這種方式在開發程式。
原本這也沒有什麼問題,我們所寫的代碼邏輯是通過我們的大腦深思熟慮組織後産生的,通常情況下我們有這個把握可以确定代碼邏輯運作的正确性,就算出現意料之外的情況, 多點幾下浏覽器的重新整理按扭也能把問題找出來解決,因為我們對代碼的運作邏輯了然于胸,自信不會出什麼叉子,一旦出現了叉子那就産生了所謂的程式BUG。
然而, 萬事總有例外, 導緻我們以往的經驗失效。 就拿我最近碰到的一件事情來說,公司有一個項目因性能優化需要,對部分功能進行技術方案調整, 重寫了代碼。代碼量不大, 功能本身的代碼和其依賴的通用函數代碼加起來一共也就二三百行,但是隐含在背後的邏輯卻異常複雜,涉及到的資料表也有五張。我将這部分需要重寫的代碼重頭至尾仔仔細細讀了一遍, 勉強能了解每一個語句塊都幹了些什麼。 可能是我邏輯思維能力不過關, 也有可能是代碼太過于複雜 , 我沒有辦法将所有這些代碼的來龍去脈全盤了然于胸,也就沒有辦法從全局的角度去梳理代碼邏輯确定優化方案,我隻能從局部的角度出發, 依樣畫葫蘆的按照舊方案重新實作一遍代碼的邏輯, 在實作的過程中如發現有優化的餘地則進行局部優化,等到足夠熟悉全局邏輯後,再從宏觀的角度對代碼結構進行調整優化,這麼做效率是低了點,卻是最保險的做法。
我照着舊代碼寫出一個個一模一樣的函數,卻沒有辦法确定這些函數的運作結果是否能得出預期的結果,鬼知道換一種語言實作以後, 函數吐出來的結果還是不是和之前的一樣,我可沒有jeff dean那樣牛逼,預判代碼的結果比編譯器還精準。本來這也不是什麼大問題,把代碼跑一遍,當執行到這些函數的調用時自然就知道結果了。問題出在這之中某些函數和代碼的入口隔着七八個調用,而且其中某些調用因為依賴于某些if條件判斷結果而不是必然被調用到的,要構造出能使這些函數被調用到的if條件判斷分支走向的參數環境是一件異常繁瑣的事情,光想想就讓人覺得煩躁和氣餒。另一種方法就是把函數的調用代碼複制一份放到執行入口的開始位置,這樣代碼一運作就直接能調用的到了。 然而, 這種方法也會帶來問題,如
函數處于不同的類和包内,調用函數需要導入包和執行個體化類,而做這些事情對項目的本身沒有實際的意義
某幾個函數隻在所在的類内被調用, 通路修飾是private, 通過這種方法測試它的準确性還需要放開權限把通路修飾聲明為public, 調試完畢後還得改回去, 操蛋
有多個分布在不同類之中的不同函數需要以類似的方式測試, 反複進行這些無意義且繁瑣的操作, 極度浪費時間,影響心情
代碼的執行入口總放着那麼一坨被注釋掉的代碼,想拿掉又怕拿掉以後下次還要用, 内心掙紮的難受
是以, 想要解決這個問題, 上面的兩種方案都不可取, 柔腸百轉也想不出像樣的解決方案。 長輩們都說程式設計都是腦力勞力, 我之前不以為然, 但當碰到這些想破腦袋也找不到辦法的問題時就不得不承認, 程式設計的确是腦力勞力。我才20歲,外表卻有30歲可以看,我想也跟長期被這些問題困擾有一定的關系(我說的是10年前的自己)
我思前想後,檢索所有腦子中關于程式設計的資源, 才找出一個之前從來沒有嘗試過的方案, 引入單元測試。我這個人有一個優點, 在工作上碰到陌生的東西從來不會望而卻步,隻要有用處, 都會去積極嘗試。對于單元測試,我雖然沒有掌握使用的方法, 但是網上查查資料, 看看教程, 我相信花不了多少功夫就能搞出來。 事實也的确如此, 隻看了一篇資料,照着教程的步驟操作就把測試程式跑起來了。 我使用的是go語言, 按照go test的規則 ,被測試的代碼所在的檔案名加上test字尾即可作為測試代碼所在的檔案的命名,如下圖

測試函數的命名方式必須要以Test作為字首, 如下圖
測試代碼編寫完成後, 在代碼所在的檔案目錄下使用cmd運作go test指令,測試代碼就可被運作了
需要測試的函數在測試代碼中被直接調用, 省去了跟蹤龐雜代碼執行走向的麻煩,從複雜的業務邏輯中解放出來, 非常的清晰友善。
從表面上看, 寫測試代碼的好處就是友善測試函數的正确性, 然而, 随着之後代碼的編寫, 我發現寫測試代碼所帶來的好處不止于此。當有了要為代碼編寫測試用例的前提條件後, 我在實作某個函數時就限制自己, 這個函數必須要友善編寫相應的測試代碼。有了這層限制以後, 我發現寫出來的代碼的品質要比不寫測試用例時高, 比如
函數的功能職責更加單一了,換言之, 函數的邏輯更穩定了, 不易産生變動, 因為我不想我辛苦編寫的測試代碼随着函數的代碼的調整而付之一炬。
不會很随意的把代碼亂放, 寫出來的代碼更加整潔,該提取函數時就建新函數, 該内聯函數時則删除不必要的函數,在之前, 為了偷懶往往會對這些細節視而不見, 這會加速代碼的腐爛。
更早的發現BUG,很多時候, 程式的BUG都是在生産環境中由使用者發現,原因很簡單, 開發項目的速度和品質這對冤家之間程式員往往會選擇前者,此外, 程式員會毫無根據的信任自己寫的代碼,是以當向程式員回報BUG時,他們都會保持懷疑的态度。很多時候, 程式員寫一個函數通常隻給一個特定的輸入,運作後發現輸出如自己預期那樣後就預設這個函數是健康的, 事實上, 當給這個函數另外的輸入時, 函數吐出的結果就在預期範圍之外, 這便導緻了BUG的産生, 個中原因便是對自己直覺盲目的信任, 認為自己的大腦就是一個人肉編譯器。 編寫測試可以很大程度上的杜絕這類問題
通常,我們會認為編寫測試是一件浪費時間的事情, 然後就是一邊向别人吹牛一邊則啪啪啪的打自己臉。 除此之此, 在開發項目時常常以邏輯不穩定随時需要調整代碼為理由拒絕寫測試,然而, 當從相反的方向來考慮問題時會發現, 有了測試的限制後,我們會更加仔細和嚴謹去編寫每一個函數 ,逼迫自己更加深入的考慮問題而防止代碼走樣, 提高代碼品質、安全性以及穩定性, 這也是寫測試所帶來的至關重要的意義。
知乎:https://www.zhihu.com/people/aspwebchh
github:https://github.com/aspwebchh
email: [email protected]