1. 程式人生 > >當我們在談論單測時我們在談論什麼

當我們在談論單測時我們在談論什麼

640?wx_fmt=gif

640?wx_fmt=jpeg

關於如何提升團隊的程式碼質量,我曾經做過很多嘗試。由於團隊成員都有繁忙的開發工作,公司也不是學校,不可能投入太多去面面俱到地教層層選拔招聘進來的程式設計師這些基礎知識,所以,做法一般是以點帶面,比如引入 sonar 程式碼檢查推動大家去掌握一些以前未曾注意到的編碼細節,比如通過針對性地培訓降低程式碼的認知複雜度讓大家掌握常用的重構技巧和設計方法。這些都能取得一定的效果,這次我想從自動化單元測試入手,更進一步……


640?wx_fmt=png

什麼是單元測試

640?wx_fmt=jpeg

教我的兒子 Allen 學習 Scratch/Python 和樂高是我週末最主要的一項“娛樂活動”,從孩子的視角去看待和學習程式語言和工程技術會給人很多意想不到的啟發。正如上面的圖片所示,有一次,Allen 在用樂高積木做一輛小車,當他把輪子組裝好的時候,他會用手指撥弄一下,看輪子能不能轉動。這時車子還遠遠沒有成形……他竟然在做單元測試!!!

Allen 的這一個舉動讓我感到驚喜,我甚至開始幻想他未來一定會成為一個出色的工程師!他明白儘可能早,儘可能小地去做單測,這已經超過我工作中見過的 80% 的軟體工程師了,在我看來,我所見過的大部分開發者做單測的時機都不夠早,單元的粒度也不夠小。

後來我看了一部名為《狼伴歸途》的電影,這部電影讓我意識到我陷入了“自己家的孩子,別人家的老婆,怎麼看都覺得好”的思維誤區裡——Allen 明白的“儘可能早,儘可能小地做單測”的這個道理,我們的祖先在石器時代就已經明白了。

640?wx_fmt=jpeg

他們在製作長矛的時候,先會用石頭打磨矛頭,每磨一段時間,他們就會將即將成形的矛頭在皮毛上劃一下,驗證矛頭是否足夠鋒利,只有矛頭合格了,才會給它裝上一個長柄。

想想我們工作中整日忙碌,把自己還不太清楚是否足夠鋒利到能刺破動物皮毛的矛頭和滿是低階缺陷的粗糙的長柄迅速組裝起來匆匆提測並交付給獵人的程式設計師們,被大家戲稱為程式“猿”其實是實至名歸——大部分人的技藝還處在非常原始的狀態,還沒有進入石器時代。

“儘可能早,儘可能小地做單測” 是我們這支人類賴以繁衍下來的最基本技能,它已經固化到 Allen 的基因裡形成了固有行為模式。


640?wx_fmt=png

為什麼要做單元測試


孩子和原始人進行單元測試是基於一種本能或傳統,他們不知道這背後的深層次原理。當我們提到測試的時候,不得不提到的是這個測試金字塔。

640?wx_fmt=png

測試金字塔有多種的分層方式,上面這幅圖引自《The Clean Coder》,不論哪種分層方式,其背後體現的經濟學原理是一致的。

首先,越是在底層的測試型別,其測試的成本越低,反饋越及時。在單元測試階段能發現的一個缺陷,假設修復它的成本是 10 塊錢,那如果它沒有在這個階段被發現,而是推遲到了元件測試階段,那修復它的成本是 100 元,以此類推,每晚一個階段發現,其修復成本都會增加一個數量級——想一想那高昂的溝通和迴歸成本吧。如果在線上才發現這些缺陷,那成本和損失就更難以估量。

基於上面的原理,專業的開發團隊應該選擇一個合理的測試策略。

首先是測試覆蓋率的要求不一樣。單元測試的覆蓋率終極目標可以追求 100%,但系統測試能做到 10% 就已經很昂貴了。

其次,是每種測試型別的關注內容應該不一樣。單元測試關心程式碼層面的正確性,大多數的異常路徑都是由單元測試來覆蓋的,單元測試應該由開發者自己來做,元件測試更多關心成功路徑的情況,以及一些明顯的極端情況、邊界狀態和可選路徑,元件測試可以由 QA 和業務人員來負責。

軟體在其生命週期內會頻繁地變更,這和建築物、飛機汽車等非常不同,充分體現了軟體”軟“的一面。在這種高度變化的環境下,要每次做到這麼高的測試覆蓋率,成本是巨大的。幸好,也正是由於軟體”軟“的一面,自動化的測試在軟體領域更容易實現。


640?wx_fmt=png

什麼是自動化單元測試


自動化單元測試 = 自動化 + 單元 + 測試

最近,我調研了一些自動化單元測試覆蓋率是個位數的應用,下面用例項來說明什麼不是自動化單元測試,然後大概就清楚了為什麼對很多開發者來說自動化單元測試那麼難。

個別 Java 開發者還在寫 main 方法,通過 System.out.println() 的方式來做單元測試, main 方法很難被自動執行, println 的結果也需要人眼去盯著判斷,顯然這種單元測試不是自動化的。

大部分開發者懂得使用 JUnit,可惜很多人用 JUnit 的原因只是需要一個更好用的 main 方法而已,他們的測試程式碼裡訪問了資料庫等有狀態的外部資源,根本無法重複地孤立地執行,所以大部分工程在使用 maven 構建的時候都設定了 -Dmaven.test.skip=true。你沒有看錯,很多人用了 JUnit 這樣的自動化測試框架,但卻不想讓它自動執行——就如點了宮保雞丁但不要雞丁一樣,他們覺得宮保雞丁裡的花生米更好吃。是的,閹割了自動化執行的 JUnit 就只剩下花生米(一個更好的 main)了。顯然,用了 JUnit,但並沒有做自動化的單元測試。

我還遇到一些高階的開發者,他們不會犯上面這些低階的錯誤,他們甚至在自動化測試方面做了很多有價值的創新。比如,他們把線上的真實入引數據抓下來,變成 XML/JSON 資料,然後基於這些資料寫單元測試。這種做法的一個主要問題是站的角度不對,這種測試是黑盒的,記得我們上一節說過吧,單元測試要覆蓋大部分異常情況,抓再多的真實資料,也很難保證覆蓋到大部分的異常,因為很多異常狀況發生概率本來就低。單元測試要覆蓋大部分異常情況就必須有一部分是站在白盒的角度來寫的。另外這種測試方式維護成本也很高,你仔細考量,它其實是測試金字塔裡的元件測試。這種測試是有價值的,但更適合 QA 團隊來負責。

以上種種都說明很多情況都做不到自動化,這是阻礙自動化單元測試落地的一個重要原因,但其實還有更深層的原因存在。


640?wx_fmt=png

如何做好自動化單元測試


這個更深層次的原因就是單元,既然單元測試位於元件測試之下,那單元的粒度比元件還要更小。要做好單元測試,首要條件是要有單元。如果元件內的程式碼沒有分成清晰獨立的小單元,那單元測試就無從談起。所以,三分測試,七分設計。

640?wx_fmt=jpeg

如果能將程式碼合理地拆分成不同的單元,你就會發現,大部分單元,如圖中綠色部分所示,都是非常獨立的,它們不依賴資料庫等外部資源,只是一個記憶體的計算,所以這部分是非常容易做自動化單元測試的。

不好做單元測試往往是膠水單元和有外部依賴的單元。而這部分程式碼往往不是業務邏輯所在,程式碼結構也比較扁平,並不複雜。

所以,當你的應用的自動化單測覆蓋率只是個位數時,先不要急著引入 MOCK 框架這類工具,當務之急是做這種單元化的改造,測試那些投入產出效果明顯的部分。以後再用 MOCK 等方式測試其他部分。

誠然,做好單元測試有很多方法、技巧和工具,但首先我們會聚焦在這一點上。


640?wx_fmt=png

最重要的事


你無法做好你不理解、不認可的事情。

這是我接觸到的優秀的敏捷教練用行動告訴我的。在做敏捷實施的時候,首先要做的是培訓敏捷的理念(你可以稱之為"洗腦"),培訓結束後,你的團隊如果無法理解和接受敏捷的核心理念,優秀的敏捷教練一般都會告訴你,敏捷其實也要因地制宜,其實你們現在團隊的做法就挺好的。

是的,如果你不認可這一篇文章所說的大部分理念,我想說的是,不做自動化單元測試也沒什麼,非常多成功的市值上千億的網際網路公司開發的軟體都有著非常低的單測覆蓋率。生命苦短,不要在單測這種小事兒上浪費時間。

作者:codeasy,志在分享哪些讓編碼變得簡單的軟體技藝——包括原則、技術、工具和實踐。

宣告:本文為作者投稿,版權歸對方所有。



 熱 文 推 薦 

☞ 和 Eclipse 並肩十年後,我終於「投敵」IDEA 了

☞ 為什麼網際網路公司需要測試人員?

☞ 爬取美團網美食資料,看北京上海都愛吃些啥

☞ 無業務不技術:那些誓用區塊鏈重塑的行業,發展怎麼樣了?

☞ 下一次 IT 變革:邊緣計算(Edge computing)

☞ 12306 脫庫 410 萬用戶資料究竟從何洩漏?

☞ 年度重磅:《AI聚變:2018年優秀AI應用案例TOP 20》正式釋出

☞ 老程式設計師肺腑忠告:千萬別一輩子靠技術生存!


  

print_r('點個好看吧!');
var_dump('點個好看吧!');
NSLog(@"點個好看吧!");
System.out.println("點個好看吧!");
console.log("點個好看吧!");
print("點個好看吧!");
printf("點個好看吧!\n");
cout << "點個好看吧!" << endl;
Console.WriteLine("點個好看吧!");
fmt.Println("點個好看吧!");
Response.Write("點個好看吧!");
alert("點個好看吧!")
echo "點個好看吧!"

640?wx_fmt=gif點選“閱讀原文”,開啟 CSDN App 閱讀更貼心!

640?wx_fmt=png 喜歡就點選“好看”吧!