走過二十年的軟件測試業

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"XML 之父 Tim Bray 在日前發表的一篇博文中,基於自身的多年軟件開發經驗,分享了關於軟件測試的那些事。“我非常確信,在我有生之年,對軟件發展的最大貢獻不是來自面向對象方法和高級語言、函數式編程、強類型、MVC 或其他任何東西,而是來自測試文化的興起。”"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"成熟的軟件開發人員非常清楚測試的重要性。但是從我的經驗來看,很多人在這方面做得還不夠。所以我寫下這篇文章的目的是要警醒行業,或許這對於我們的從業人員來說本是多此一舉,但現實顯然不是這樣。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文的靈感來自於 Justin Searls 的兩個 Twitter 帖子,這裏引據其中的幾句話:“你聽到的幾乎所有關於軟件測試的建議都是糟糕的。這些建議要麼看上去就很糟糕,要麼會導致糟糕的結果,要麼會讓你專注於錯誤的事情(通常是工具),結果分散了注意力”,“幾乎沒有團隊會編寫富有表現力的測試、建立清晰的界限、快速可靠地運行,並且只會因有用的原因失敗。大家的重點都錯了。”(注意:Justin 顯然身處測試行業。)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Twitter 帖子都彎彎繞繞、很難摸清脈絡,所以我從中貼兩張截圖出來。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/bb\/bb2aac30993a4eda59a2dbe38b242c44.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/14\/1492c1ad7789766d3e040668f1a93c7f.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我先亮明我的觀點:我認爲這些不規則的所謂的“結構圖”是大錯特錯,而且影響嚴重。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"背景"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"自 1979 年以來,我一直從事軟件開發工作。雖然我的觀點很可能是錯的,但這並不是因爲我缺乏經驗。我做過的幾乎所有有意義的工作都是底層基礎設施的東西:解析器、消息路由器、數據可視化框架、網絡爬蟲、全文搜索。所以如果你不在基礎設施領域,我的一些發現可能就不那麼有說服力了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在我編程生涯的前 20 年,比如說直到 2000 年,行業內幾乎沒有軟件測試的位置。一個後果是,如同 Gerald Weinberg 經常被引用的一句話:“如果建築師按照程序員編寫程序的方式建造建築物,那麼飛來的第一隻啄木鳥就會摧毀整個文明。”"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那時,對於自己寫的任何軟件,我總會在幾年後開始討厭它,因爲它變得越來越脆弱和可怕。現在回頭想想,我抗拒的大概是那些未經測試的代碼給人帶來的體驗,因爲經常會有一些小更改由於難以理解的原因意外而引發“大災難”。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2000 至 2010 年的某個時候,情況開始發生變化。我的看法是,最初的推動力或多或少來自 Ruby 社區,並隨着 Rails 的興起而加速。我開始聽到“測試的感染”(test-infected)這個詞,我注意到如果提交的代碼沒有像樣的單元測試,它們很容易被無情拒絕。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其他人告訴我,他們最初被圍繞 Martin Fowler 的《重構》一書中的討論打動,這本書最早出版於 1999 年,告訴大家你無法真正重構未經測試的代碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"特別是我記得自己在 2010 年參加了蘇格蘭 Ruby 技術大會,會上似乎有一半的演講是關於測試最佳實踐和技術的。我在那裏學到了很多我今天仍在應用的知識。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"我非常確信,在我有生之年,對軟件發展的最大貢獻不是來自面向對象方法和高級語言、函數式編程、強類型、MVC 或其他任何東西"},{"type":"text","text":" ,"},{"type":"text","marks":[{"type":"strong"}],"text":"而是來自測試文化的興起"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"我的信念"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們現在做事的方式好得多了。用回前面建築師和程序員的比喻來說,文明不用再害怕啄木鳥了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"例如:我在谷歌和 AWS 工作的那些年裏,我們遇到過很多中斷和故障,但很少是因爲某個軟件錯誤這樣簡單的原因造成的。拙劣的部署、造成障礙的錯誤配置、證書問題(真是讓人頭疼)、DNS 小問題、實習生使用 Python 腳本做負載測試、金絲雀故障……痛苦的記憶來自很多因素,但往往不會僅因爲一個小錯誤。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我不記得我是什麼時候被“感染”的,但我可以保證,一旦你被感染,你永遠不會容忍未經測試的代碼了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"是的,你可以在上過公共廁所後不洗手;是的,你可以用手指喫意大利麪,但負責任的成年人不會做這些事情,他們也不會交付未經測試的代碼。順便說一句,我後來也不再討厭我開發了一段時間的軟件了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"隨着時間的流逝,我對糟糕測試的容忍度越來越低。我阻止別人升職、給別人打低分、斥責高級開發經理,而且一般都沒得商量。我可以不樹敵,可以容忍(大多數)情況,因爲我尊重他人、待人友善和富有同情心。但在這個問題上我不會後退。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,我至死都會堅持這項原則(呃,我想應該是一系列原則):"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"單元測試是對軟件未來的一項必不可少的投資。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"測試覆蓋率數據很有用,你應該密切關注它。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"未經測試的老舊代碼庫可以而且應該逐步改進"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"單元測試需要使用單組 IDE 組合鍵非常快速地運行,並且完全可以像打寒戰一樣每隔幾秒鐘運行一次。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"測試教毫無意義;只做有用的事情。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"單元測試賦予代碼審查者權力。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":7,"align":null,"origin":null},"content":[{"type":"text","text":"集成測試非常重要且非常困難,尤其是在微服務環境中。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":8,"align":null,"origin":null},"content":[{"type":"text","text":"集成測試需要 100% 通過,有失敗被忽略是不行的。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":9,"align":null,"origin":null},"content":[{"type":"text","text":"集成測試需要運行得“足夠快”。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":10,"align":null,"origin":null},"content":[{"type":"text","text":"加入基準測試對測試很有好處。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在我將擴展上述列表中的聲明。其中一些不需要進一步的拓展(例如“單元測試應該運行得很快”)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但首先… 你能證明測試的有效性嗎?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"哦,不能。我四處尋找有關測試效果的高質量研究,但沒有找到什麼結果。這並不令人驚訝。因爲你需要找到兩個強大的團隊來執行重要的開發任務,並且在規模、結構、工具、技能水平和工作實踐——在除測試之外的所有方面的表現都大致相同。然後,還需要在十年或更長的週期內研究他們的生產力和質量差異。據我所知,從來沒有人這樣做過,對這一結論我是很有信心。所以我們只剩下了經驗積累,Nero Wolfe 稱之爲“經驗總結出來的智慧”。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"單元測試,現在和以後都很重要"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當你創建一個新特性並實現一系列函數來完成它時,不要自欺欺人地認爲你足夠聰明,提前知道哪些東西容易出錯,哪些將成爲瓶頸,哪些將是你的繼任者難以理解的。畢竟沒有人足夠聰明!因此,只要不是單行代碼的內容都要編寫測試。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面那張 Spotify 的圖中“實現細節”的標籤是反對單元測試的,這讓我很不爽。我在這裏嗅到了不接地氣的架構師的味道,這些人認爲所有的工作就是在白板上正確地放置方框和箭頭,而不是親手去寫分號和 if 語句。如果你的基礎微服務代碼沒有經過充分測試,那麼你就是在聚沙成塔而已。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在經過良好單元測試的代碼庫中工作會給開發人員帶來勇氣。如果重新實現一兩個 API 會帶來一些小的改變,那麼你可以大膽一點去做。因爲有了良好的單元測試,即使你搞砸了,你也會很快就發現問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"請記住,代碼的讀取和更新頻率高於編寫頻率。我個人認爲,好的測試在第一次開發過程中就可以幫上開發人員的忙,並且不會減慢他們的速度。就我對這一職業的瞭解,單元測試爲將要學習和修改這些代碼的後續開發人員們,帶來了顯著的生產力提升並減輕了他們的痛苦。這就是業務價值所在!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那我們是否可以在哪裏放寬單元測試覆蓋率呢?比如早在 2012 年,我就寫過關於測試 UI 代碼的文章,尤其是關於移動 UI 代碼。給它們編寫測試太難了,在某些情況下可能不是一項好的投資。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另一個例子是 Java 世界專屬的,存在依賴注入框架的情況下,你會得到非常大的文件,其中包含數以千計的配置亂碼[*cough*SpringBoot*cough*],生命有限,實在沒時間研究它們。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"還有一些非常罕見的異常處理場景,你的數據中心很可能在遇到它們之前就陷入了困境,此時 IOException 會是你手頭的那堆麻煩裏最不起眼的,所以,也許我們不應該沉迷於那些 if err != nil 子句。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"覆蓋率數據"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我並不會強求代碼庫應該達到某個覆蓋率。但是這些數據其實很有用,你應該注意下它。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,找到特殊情況——覆蓋率明顯過低(或高)的文件,然後查找簽入之間的更改。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"覆蓋率數據不僅僅是一個百分比數字。當我基本完成某段代碼時,我喜歡運行一個測試,開啓覆蓋,然後快速瀏覽所有重要的代碼塊,查看綠色和紅色的側欄。每次這樣做我都會得到驚喜:往往在有些文件上我本以爲我的單元測試很聰明,但覆蓋率實際上差了很多。這不僅讓我想要改進測試,它還教會了我一些我原不知道的關於我的代碼如何對輸入做出反應的知識。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"話雖如此,我非常尊重一些軟件團隊,他們有嚴格的覆蓋率要求並能堅持下去。AWS 有一個團隊,在他們的 CI\/CD 管道中實際上有一個 100% 覆蓋率的 blocking 檢查。我不確定這是否合理,但這些人正在基礎設施的關鍵部分編寫非常底層的代碼,在這種情況下不合理的東西可能也是合理的。而且他們比我聰明。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外,我經歷過的所有團隊工作,都會遇到一些測試不足、拖累工作的遺留代碼。即使是像我這樣的測試狂也不會要求別人將高覆蓋率單元測試改裝到那些破東西上。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我見過一項很成功的策略,它有兩個部分:首先,當你對沒有單元測試的函數進行任何重大更改時,請編寫單元測試。其次,導致覆蓋率下降的簽入是不允許的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這很有效,因爲當你在應對大型老舊代碼庫時,更新通常不會均勻地分散在其中,代碼庫中會有一些有用行爲聚集的熱點。所以如果你應用這個策略,代碼“熱區”的測試覆蓋率會有機增長,達到相當不錯的水平,而其他代碼可能多年沒有人接觸或查看過,它們被忽略掉也沒關係。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"“不要宗教”"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"測試應該是最務實的活動,沒有意識形態介入的餘地。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"請不要跟我扯什麼 mock、stub、fake,沒人在乎。在一個相關主題上,當我發現很多人在針對 DynamoDB 運行代碼的單元測試中使用 DynamoDB Local 時,我感到非常驚訝。但它真的很有效,速度很快,而且比編寫另一個 mock 或設置一個到實際雲服務的鏈接要省事很多。不要教條主義!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後來談 TDD\/BDD 信仰。有時,對於某些人來說,它很有用,給他們帶來了更多力量。但對我來說它的純粹形式從來沒什麼用,因爲我的編碼風格在早期階段往往是混亂的,我一直在不斷地重構和重構函數。如果我在開始編寫它們之前就知道我想讓它們做什麼,那麼 TDD 可能是有意義的。另一方面,當我已經草擬了一組我認爲合理的方法,並且正在爲基本代碼編寫測試時,我會提前準備併爲還沒寫出來的代碼編寫更多測試。我這個樣子沒法成爲 TDD“教會成員”,但我不在乎。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"還有另一種信仰:在 Java 中對私有方法進行單元測試並不容易,Java 錯了。有些人聲稱你不應該測試這些方法,因爲它們不是類合約的一部分,那些人也錯了。爲了方便測試,妥協封裝並讓方法非私有是完全合理的。或者出於同樣的原因應該編寫 API 來獲取接口,而非類對象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當你針對複雜的 API 運行大量測試時,很容易就可以編寫一個 "},{"type":"text","marks":[{"type":"strong"}],"text":"runTest()"},{"type":"text","text":" 幫助程序,將正確佈置參數並針對結果運行標準化檢查。如果你不這樣做,你最終會得到很多重複的剪切粘貼代碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏有爭論的餘地,沒有教條的空間,我通常對此不大同意,因爲當我更改了某些東西、並且是我以前從未見過的單元測試失敗時,我不想在弄清楚發生了什麼之前還得去搞清楚一堆幫助程序。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"無論如何,如果你的工程師正在編寫帶有有效測試的代碼,請不要跟他們講任何廢話。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"審查人是你的朋友"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有一次一個同事來找我尋求幫助,查看之後發現他們的問題很棘手。然後我讓他們向我展示代碼庫,我提出了一些審查請求。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我看的前幾段代碼沒有單元測試,但確實有註釋說“稍後進行單元測試”。我走進他們的團隊房間說:“夥計們,我們現在需要談談。”"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在這裏我想強調的是:單元測試一旦推後,就不會有人做了!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以及重點是,代碼審查的目的不是正確性檢查。審查人有權假設代碼是有效的。審查人應該檢查 O(N3) 瓶頸、可讀性問題、笨拙的函數參數、不穩定的錯誤處理等。如果你沒有足夠的測試來證明你的代碼的基本正確性,那麼要求審查人考慮這些事情是不公平的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"進一步地說,我在審查時經常會遇到這樣的情況,我很難弄清楚開發人員到底想在某段代碼中完成什麼。但首先,我會轉到單元測試並查看它在做什麼,因爲有時從這裏就可以明顯看出開發人員所設想的函數用途。這也適用於需要修改代碼的後續開發人員。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"集成測試"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"做出本文前面所展示的圖片的人似乎都認爲這很重要,當然他們是對的。不過,我不確定“集成”和“端到端”之間的區別是否那麼重要。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"問題是我們在從單體遷移到微服務,於是這些測試變得更加重要,但也讓它們更難構建。如果可以,這是堅持使用簡單的單體應用的另一個很好的理由。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這反過來意味着你必須確保爲你的集成測試規劃時間,包括設計和維護時間。(單元測試只是基本編程預算的一部分。)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我知道這些測試很難寫,我曾與其他優秀的團隊一起工作,但他們的集成測試都很糟糕。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不好的一面是它們需要跑幾個小時,這個就沒什麼好說的,因爲時間目標經常沒法達成。我們這麼說吧:集成測試不需要像單元測試一樣快,但它們確實需要足夠快,這樣你就可以在去上廁所或喝咖啡,或被聊天窗口打斷時運行它們了。不過這還是很難實現的目標。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後,我一次又一次地看到集成測試日誌顯示很多失敗,一些開發人員會說“哦,是的,那些測試是不穩定的,它們有時會失敗。”出於某種原因,他們認爲這是可以的。要麼測試執行了一些可能在生產中失敗的任務,在這種情況下你應該將失敗視爲 blocker,或者這些任務不會在生產中復現,在這種情況下你應該將它們從該死的測試套件中取出,然後測試就會運行得更快了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"基準測試"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲我總是在處理對性能非常敏感的代碼,所以我經常會編寫基準測試,一段時間後我養成了將其中一些留在測試套件中的習慣。因爲我已經觀察到很多由性能下降引起的中斷,比如某個配置改動將 TLS 計算從硬件推入 Java 字節碼這樣的蠢事。你真的會希望提前發現這種情況。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"工具鏈"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可用的工具有很多,足夠用了。讓你的團隊就他們將要使用的內容達成一致,併成爲相應的專家,然後不要把你的問題歸咎於工具上。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"我們的處境"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我認爲我們的整體情況還不錯,因爲大多數理智的組織都開始表現出相當好的測試紀律,尤其是在服務端代碼方面。就像我說的,我在生產代碼中看到的錯誤比以前少了很多。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而且每個團隊都必須與那些可怕的、未經檢驗的、停滯不前的遺留代碼池作鬥爭。打起精神來吧,處理它們只是工作的一部分。而且不管怎樣,你可能也寫過那樣的代碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但每天總有團隊會迷失方向,“開始在上完廁所後不洗手”。不要這樣做,並且不要發佈未經測試的代碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"關於作者"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Tim Bray,全名 Timothy William Bray,有“XML 之父”之稱,XML 和 Atom 標準的創建者,曾先後就職於 DEC、Sun、Google 等公司。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"原文鏈接:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/www.tbray.org\/ongoing\/When\/202x\/2021\/05\/15\/Testing-in-2021","title":"","type":null},"content":[{"type":"text","text":"https:\/\/www.tbray.org\/ongoing\/When\/202x\/2021\/05\/15\/Testing-in-2021"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章