XML之父:不對程式碼做測試就像“上完廁所不洗手”

「來源: |InfoQ ID:infoqchina」

XML之父:不對程式碼做測試就像“上完廁所不洗手”

作者|Tim Bray

翻譯|王強

審校|燕珊

策劃|Tina

XML 之父 Tim Bray 在日前發表的一篇博文中,基於自身的多年軟體開發經驗,分享了關於軟體測試的那些事。“我非常確信,在我有生之年,對軟體發展的最大貢獻不是來自面向物件方法和高階語言、函數語言程式設計、強型別、MVC 或其他任何東西,而是來自測試文化的興起。”

成熟的軟體開發人員非常清楚測試的重要性。但是從我的經驗來看,很多人在這方面做得還不夠。所以我寫下這篇文章的目的是要警醒行業,或許這對於我們的從業人員來說本是多此一舉,但現實顯然不是這樣。

本文的靈感來自於 Justin Searls 的兩個 Twitter 帖子,這裡引據其中的幾句話:“你聽到的幾乎所有關於軟體測試的建議都是糟糕的。這些建議要麼看上去就很糟糕,要麼會導致糟糕的結果,要麼會讓你專注於錯誤的事情(通常是工具),結果分散了注意力”,“幾乎沒有團隊會編寫富有表現力的測試、建立清晰的界限、快速可靠地執行,並且只會因有用的原因失敗。大家的重點都錯了。”(注意:Justin 顯然身處測試行業。)

Twitter 帖子都彎彎繞繞、很難摸清脈絡,所以我從中貼兩張截圖出來。

XML之父:不對程式碼做測試就像“上完廁所不洗手”

XML之父:不對程式碼做測試就像“上完廁所不洗手”

我先亮明我的觀點:我認為這些不規則的所謂的“結構圖”是大錯特錯,而且影響嚴重。

背景

自 1979 年以來,我一直從事軟體開發工作。雖然我的觀點很可能是錯的,但這並不是因為我缺乏經驗。我做過的幾乎所有有意義的工作都是底層基礎設施的東西:解析器、訊息路由器、資料視覺化框架、網路爬蟲、全文搜尋。所以如果你不在基礎設施領域,我的一些發現可能就不那麼有說服力了。

在我程式設計生涯的前 20 年,比如說直到 2000 年,行業內幾乎沒有軟體測試的位置。一個後果是,如同 Gerald Weinberg 經常被引用的一句話:“如果建築師按照程式設計師編寫程式的方式建造建築物,那麼飛來的第一隻啄木鳥就會摧毀整個文明。”

那時,對於自己寫的任何軟體,我總會在幾年後開始討厭它,因為它變得越來越脆弱和可怕。現在回頭想想,我抗拒的大概是那些未經測試的程式碼給人帶來的體驗,因為經常會有一些小更改由於難以理解的原因意外而引發“大災難”。

2000 至 2010 年的某個時候,情況開始發生變化。我的看法是,最初的推動力或多或少來自 Ruby 社群,並隨著 Rails 的興起而加速。我開始聽到“測試的感染”(test-infected)這個詞,我注意到如果提交的程式碼沒有像樣的單元測試,它們很容易被無情拒絕。

其他人告訴我,他們最初被圍繞 Martin Fowler 的《重構》一書中的討論打動,這本書最早出版於 1999 年,告訴大家你無法真正重構未經測試的程式碼。

特別是我記得自己在 2010 年參加了蘇格蘭 Ruby 技術大會,會上似乎有一半的演講是關於測試最佳實踐和技術的。我在那裡學到了很多我今天仍在應用的知識。

我非常確信,在我有生之年,對軟體發展的最大貢獻不是來自面向物件方法和高階語言、函數語言程式設計、強型別、MVC 或其他任何東西

而是來自測試文化的興起

我的信念

我們現在做事的方式好得多了。用回前面建築師和程式設計師的比喻來說,文明不用再害怕啄木鳥了。

例如:我在谷歌和 AWS 工作的那些年裡,我們遇到過很多中斷和故障,但很少是因為某個軟體錯誤這樣簡單的原因造成的。拙劣的部署、造成障礙的錯誤配置、證書問題(真是讓人頭疼)、DNS 小問題、實習生使用 Python 指令碼做負載測試、金絲雀故障……痛苦的記憶來自很多因素,但往往不會僅因為一個小錯誤。

我不記得我是什麼時候被“感染”的,但我可以保證,一旦你被感染,你永遠不會容忍未經測試的程式碼了。

是的,你可以在上過公共廁所後不洗手;是的,你可以用手指吃義大利麵,但負責任的成年人不會做這些事情,他們也不會交付未經測試的程式碼。順便說一句,我後來也不再討厭我開發了一段時間的軟體了。

隨著時間的流逝,我對糟糕測試的容忍度越來越低。我阻止別人升職、給別人打低分、斥責高階開發經理,而且一般都沒得商量。我可以不樹敵,可以容忍(大多數)情況,因為我尊重他人、待人友善和富有同情心。但在這個問題上我不會後退。

所以,我至死都會堅持這項原則(呃,我想應該是一系列原則):

單元測試是對軟體未來的一項必不可少的投資。

測試覆蓋率資料很有用,你應該密切關注它。

未經測試的老舊程式碼庫可以而且應該逐步改進

單元測試需要使用單組 IDE 組合鍵非常快速地執行,並且完全可以像打寒戰一樣每隔幾秒鐘執行一次。

測試教毫無意義;只做有用的事情。

單元測試賦予程式碼審查者權力。

整合測試非常重要且非常困難,尤其是在微服務環境中。

整合測試需要 100% 透過,有失敗被忽略是不行的。

整合測試需要執行得“足夠快”。

加入基準測試對測試很有好處。

現在我將擴充套件上述列表中的宣告。其中一些不需要進一步的拓展(例如“單元測試應該執行得很快”)。

但首先…… 你能證明測試的有效性嗎?

哦,不能。我四處尋找有關測試效果的高質量研究,但沒有找到什麼結果。這並不令人驚訝。因為你需要找到兩個強大的團隊來執行重要的開發任務,並且在規模、結構、工具、技能水平和工作實踐——在除測試之外的所有方面的表現都大致相同。然後,還需要在十年或更長的週期內研究他們的生產力和質量差異。據我所知,從來沒有人這樣做過,對這一結論我是很有信心。所以我們只剩下了經驗積累,Nero Wolfe 稱之為“經驗總結出來的智慧”。

單元測試,現在和以後都很重要

當你建立一個新特性並實現一系列函式來完成它時,不要自欺欺人地認為你足夠聰明,提前知道哪些東西容易出錯,哪些將成為瓶頸,哪些將是你的繼任者難以理解的。畢竟沒有人足夠聰明!因此,只要不是單行程式碼的內容都要編寫測試。

上面那張 Spotify 的圖中“實現細節”的標籤是反對單元測試的,這讓我很不爽。我在這裡嗅到了不接地氣的架構師的味道,這些人認為所有的工作就是在白板上正確地放置方框和箭頭,而不是親手去寫分號和 if 語句。如果你的基礎微服務程式碼沒有經過充分測試,那麼你就是在聚沙成塔而已。

在經過良好單元測試的程式碼庫中工作會給開發人員帶來勇氣。如果重新實現一兩個 API 會帶來一些小的改變,那麼你可以大膽一點去做。因為有了良好的單元測試,即使你搞砸了,你也會很快就發現問題。

請記住,程式碼的讀取和更新頻率高於編寫頻率。我個人認為,好的測試在第一次開發過程中就可以幫上開發人員的忙,並且不會減慢他們的速度。就我對這一職業的瞭解,單元測試為將要學習和修改這些程式碼的後續開發人員們,帶來了顯著的生產力提升並減輕了他們的痛苦。這就是業務價值所在!

那我們是否可以在哪裡放寬單元測試覆蓋率呢?比如早在 2012 年,我就寫過關於測試 UI 程式碼的文章,尤其是關於移動 UI 程式碼。給它們編寫測試太難了,在某些情況下可能不是一項好的投資。

另一個例子是 Java 世界專屬的,存在依賴注入框架的情況下,你會得到非常大的檔案,其中包含數以千計的配置亂碼[*cough*SpringBoot*cough*],生命有限,實在沒時間研究它們。

還有一些非常罕見的異常處理場景,你的資料中心很可能在遇到它們之前就陷入了困境,此時 IOException 會是你手頭的那堆麻煩裡最不起眼的,所以,也許我們不應該沉迷於那些 if err != nil 子句。

覆蓋率資料

我並不會強求程式碼庫應該達到某個覆蓋率。但是這些資料其實很有用,你應該注意下它。

首先,找到特殊情況——覆蓋率明顯過低(或高)的檔案,然後查詢簽入之間的更改。

覆蓋率資料不僅僅是一個百分比數字。當我基本完成某段程式碼時,我喜歡執行一個測試,開啟覆蓋,然後快速瀏覽所有重要的程式碼塊,檢視綠色和紅色的側欄。每次這樣做我都會得到驚喜:往往在有些檔案上我本以為我的單元測試很聰明,但覆蓋率實際上差了很多。這不僅讓我想要改進測試,它還教會了我一些我原不知道的關於我的程式碼如何對輸入做出反應的知識。

話雖如此,我非常尊重一些軟體團隊,他們有嚴格的覆蓋率要求並能堅持下去。AWS 有一個團隊,在他們的 CI/CD 管道中實際上有一個 100% 覆蓋率的 blocking 檢查。我不確定這是否合理,但這些人正在基礎設施的關鍵部分編寫非常底層的程式碼,在這種情況下不合理的東西可能也是合理的。而且他們比我聰明。

另外,我經歷過的所有團隊工作,都會遇到一些測試不足、拖累工作的遺留程式碼。即使是像我這樣的測試狂也不會要求別人將高覆蓋率單元測試改裝到那些破東西上。

我見過一項很成功的策略,它有兩個部分:首先,當你對沒有單元測試的函式進行任何重大更改時,請編寫單元測試。其次,導致覆蓋率下降的簽入是不允許的。

這很有效,因為當你在應對大型老舊程式碼庫時,更新通常不會均勻地分散在其中,程式碼庫中會有一些有用行為聚集的熱點。所以如果你應用這個策略,程式碼“熱區”的測試覆蓋率會有機增長,達到相當不錯的水平,而其他程式碼可能多年沒有人接觸或檢視過,它們被忽略掉也沒關係。

“不要宗教”

測試應該是最務實的活動,沒有意識形態介入的餘地。

請不要跟我扯什麼 mock、stub、fake,沒人在乎。在一個相關主題上,當我發現很多人在針對 DynamoDB 執行程式碼的單元測試中使用 DynamoDB Local 時,我感到非常驚訝。但它真的很有效,速度很快,而且比編寫另一個 mock 或設定一個到實際雲服務的連結要省事很多。不要教條主義!

然後來談 TDD/BDD 信仰。有時,對於某些人來說,它很有用,給他們帶來了更多力量。但對我來說它的純粹形式從來沒什麼用,因為我的編碼風格在早期階段往往是混亂的,我一直在不斷地重構和重構函式。如果我在開始編寫它們之前就知道我想讓它們做什麼,那麼 TDD 可能是有意義的。另一方面,當我已經草擬了一組我認為合理的方法,並且正在為基本程式碼編寫測試時,我會提前準備併為還沒寫出來的程式碼編寫更多測試。我這個樣子沒法成為 TDD“教會成員”,但我不在乎。

還有另一種信仰:在 Java 中對私有方法進行單元測試並不容易,Java 錯了。有些人聲稱你不應該測試這些方法,因為它們不是類合約的一部分,那些人也錯了。為了方便測試,妥協封裝並讓方法非私有是完全合理的。或者出於同樣的原因應該編寫 API 來獲取介面,而非類物件。

當你針對複雜的 API 執行大量測試時,很容易就可以編寫一個

runTest()

幫助程式,將正確佈置引數並針對結果執行標準化檢查。如果你不這樣做,你最終會得到很多重複的剪下貼上程式碼。

這裡有爭論的餘地,沒有教條的空間,我通常對此不大同意,因為當我更改了某些東西、並且是我以前從未見過的單元測試失敗時,我不想在弄清楚發生了什麼之前還得去搞清楚一堆幫助程式。

無論如何,如果你的工程師正在編寫帶有有效測試的程式碼,請不要跟他們講任何廢話。

審查人是你的朋友

有一次一個同事來找我尋求幫助,檢視之後發現他們的問題很棘手。然後我讓他們向我展示程式碼庫,我提出了一些審查請求。

我看的前幾段程式碼沒有單元測試,但確實有註釋說“稍後進行單元測試”。我走進他們的團隊房間說:“夥計們,我們現在需要談談。”

在這裡我想強調的是:單元測試一旦推後,就不會有人做了!

以及重點是,程式碼審查的目的不是正確性檢查。審查人有權假設程式碼是有效的。審查人應該檢查 O(N3) 瓶頸、可讀性問題、笨拙的函式引數、不穩定的錯誤處理等。如果你沒有足夠的測試來證明你的程式碼的基本正確性,那麼要求審查人考慮這些事情是不公平的。

進一步地說,我在審查時經常會遇到這樣的情況,我很難弄清楚開發人員到底想在某段程式碼中完成什麼。但首先,我會轉到單元測試並檢視它在做什麼,因為有時從這裡就可以明顯看出開發人員所設想的函式用途。這也適用於需要修改程式碼的後續開發人員。

整合測試

做出本文前面所展示的圖片的人似乎都認為這很重要,當然他們是對的。不過,我不確定“整合”和“端到端”之間的區別是否那麼重要。

問題是我們在從單體遷移到微服務,於是這些測試變得更加重要,但也讓它們更難構建。如果可以,這是堅持使用簡單的單體應用的另一個很好的理由。

這反過來意味著你必須確保為你的整合測試規劃時間,包括設計和維護時間。(單元測試只是基本程式設計預算的一部分。)

我知道這些測試很難寫,我曾與其他優秀的團隊一起工作,但他們的整合測試都很糟糕。

不好的一面是它們需要跑幾個小時,這個就沒什麼好說的,因為時間目標經常沒法達成。我們這麼說吧:整合測試不需要像單元測試一樣快,但它們確實需要足夠快,這樣你就可以在去上廁所或喝咖啡,或被聊天視窗打斷時執行它們了。不過這還是很難實現的目標。

最後,我一次又一次地看到整合測試日誌顯示很多失敗,一些開發人員會說“哦,是的,那些測試是不穩定的,它們有時會失敗。”出於某種原因,他們認為這是可以的。要麼測試執行了一些可能在生產中失敗的任務,在這種情況下你應該將失敗視為 blocker,或者這些任務不會在生產中復現,在這種情況下你應該將它們從該死的測試套件中取出,然後測試就會執行得更快了。

基準測試

因為我總是在處理對效能非常敏感的程式碼,所以我經常會編寫基準測試,一段時間後我養成了將其中一些留在測試套件中的習慣。因為我已經觀察到很多由效能下降引起的中斷,比如某個配置改動將 TLS 計算從硬體推入 Java 位元組碼這樣的蠢事。你真的會希望提前發現這種情況。

工具鏈

可用的工具有很多,足夠用了。讓你的團隊就他們將要使用的內容達成一致,併成為相應的專家,然後不要把你的問題歸咎於工具上。

我們的處境

我認為我們的整體情況還不錯,因為大多數理智的組織都開始表現出相當好的測試紀律,尤其是在服務端程式碼方面。就像我說的,我在生產程式碼中看到的錯誤比以前少了很多。

而且每個團隊都必須與那些可怕的、未經檢驗的、停滯不前的遺留程式碼池作鬥爭。打起精神來吧,處理它們只是工作的一部分。而且不管怎樣,你可能也寫過那樣的程式碼。

但每天總有團隊會迷失方向,“開始在上完廁所後不洗手”。不要這樣做,並且不要釋出未經測試的程式碼。

關於作者

Tim Bray,全名 Timothy William Bray,有“XML 之父”之稱,XML 和 Atom 標準的建立者,曾先後就職於 DEC、Sun、Google 等公司。

原文連結:

https://www。tbray。org/ongoing/When/202x/2021/05/15/Testing-in-2021