1. 程式人生 > 其它 >Google軟體工程中文譯文-第11章-第一節

Google軟體工程中文譯文-第11章-第一節

第11章 測試概覽

 

Adam Bender 撰寫

Tom Manshreck 編輯

 

    測試工作一直是程式設計工作的一部分。事實上,當第一次寫計算機程式的時候,你幾乎肯定會給程式扔一些資料,看看執行結果是否符合預期。很長時間以來,軟體測試的工作方法形成了一種非常相似的過程,很大程度是手工完成,很容易出錯。然而,自從2000年左右,軟體行業的測試方法有巨大的提升,能夠應對現代軟體系統的規模和複雜度。這種演化的關鍵,就是開發者驅動的、自動化的測試實踐。

    自動化測試可以防止bug逃逸影響到你的使用者。Bug在開發階段發現的越晚,代價就越高,在很多情況下是指數級的。但是,“捉bug”只是動機的一部分。另一個同等重要的原因是支援變化的能力。無論是增加新的特性,還是針對程式碼健康度的重構,或者是完成一個更大的重新設計,自動化測試都能快速捕獲錯誤,這樣就會使變更軟體更有信心。

    迭代更快的公司能更快適應變化的技術、市場條件和客戶口味。如果你有一個靠譜的測試實踐,你就不用擔心變化——你可以擁抱變化,把它作為開發軟體的核心品質。你想更多更快地改變你的系統,你越需要一個更快的方法進行測試。

    寫測試的行為也會改善系統的設計。作為你自己程式碼的第一批客戶,測試可以告訴你很多關於測試選擇的事情。你的系統是不是和資料庫過度耦合了?API支援需要的用例嗎?你的系統處理了所有的邊界用例嗎?寫自動化的測試強迫你在開發週期早期面對這些問題。這樣做通常會獲得更加模組化的軟體,併為未來賦予更大的靈活性。

    在測試軟體的主題上我們已經頗費筆墨,這也是有原因的:對於這樣重要的實踐,對於很多人來說,想把它做好卻是一項難以理解的手藝。在谷歌,雖然我們已經做了這麼久,我們在把流程穩步地擴充套件到整個公司的時候,依然面臨困難的問題。在本章中,我們分享一下我們幫助推進對話的過程中學到的東西。

我們為什麼要寫測試?

    為了更好地理解如何發揮測試工作的最大功效,讓我們從頭說起。當我們談論自動化測試的時候,我們到底在談論什麼?

    最簡單的測試定義由如下條目定義:

  • 你正在測試的一個簡單的行為,通常是一個你呼叫的方法或API
  • 你傳入這個API的一個具體的輸入,一些值
  • 一個可以觀察的輸出或行為
  • 一個可控的環境,例如一個獨立隔離的程序

    當你執行一個這樣的測試的時候,把輸入傳入到系統,驗證輸出,你就會知道系統行為是否符合預期。當放在一起的時候,成百上千簡單的測試(通常叫做一個測試集(test suite))會告訴你整體的產品是符合設計預期的,並且更重要的是,它也能告訴你什麼時候不是。

 

    建立和維護健康的測試集需要很多努力。隨著程式碼庫增長,測試集也會增長。測試集會開始面臨類似不穩定和緩慢的挑戰。這些挑戰如果不能良好的解決,那麼這個測試集就會嚴重受損。一定要明白,測試的價值來源於工程師對於它們的信任程度。如果測試工作成了生產力窪地,常常帶來勞累和不確定性,工程師就會失去信任,轉而去找變通方法。一個差的測試集會比徹底沒有測試集更糟糕。

 

除了助力公司快速構建很棒的產品之外,測試工作在確保生活中重要的產品和服務安全性方面正在變得至為重要。如今,軟體更深度的融入到我們的生活之中,軟體缺陷不僅僅是導致一點點麻煩那麼簡單,它們會耗費大量的金錢,導致資產的損失,甚至最嚴重的付出生命的代價。

 

在谷歌,我們已經下定決心:測試工作不能是馬後炮。聚焦質量,測試是我們如何完成工作的一部分。我們知道,有時是痛苦的認識到,如果不把質量作為我們的產品和服務的一部分,將會不可避免地導致糟糕的結果。因此,我們把測試工作作為我們工程師文化的核心部分。

Google Web Server的故事

在谷歌早期,大家一般認為工程師驅動的測試工作不太重要。團隊通常依賴聰明的傢伙把軟體做好。很少部分系統執行大的整合測試,大部分都是裸奔。有一個產品似乎最難受:這個產品叫做Google Web Server(谷歌網頁伺服器),也稱作GWS。

 

GWS是服務於谷歌搜尋查詢的網頁伺服器,它對於谷歌搜尋的重要性不亞於機場的空中交通管制。時間回到2005年,隨著專案規模和複雜度的膨脹,生產率急劇下降。版本的bug越來越多,也需要越來越長的時間修復。團隊成員變更服務時信心不足,經常是在生產環境功能不可用的時候才發現出了錯。(那段時間,超過80%的生產推送包含影響使用者的bug,而不得不回滾。)

 

為了解決這些問題,GWS的技術負責人決定製定一個工程師驅動的、自動化測試的機制。作為這個機制的一部分,所有新的程式碼變更都要求包含測試,並且測試會持續執行。在引入這個機制一年之內,緊急修復的數量下降了一半,即使這個專案每個季度都有創紀錄的變更發生。即便面對空前的增長和變化,引入的測試在谷歌為最為關鍵的專案之一注入了生產力和信心。今天,GWS有數萬個測試,每天幾乎都發布,很少有客戶可見的故障。

 

GWS的變化標誌了谷歌測試文化的分水嶺,因為公司其他部門的團隊看到了測試的好處,也行動起來採取相似的策略。

 

GWS經驗給到我們最關鍵的洞察之一是你不能只依賴語法能力來避免產品缺陷。即使每個工程師只是偶爾寫出bug,在一個專案上你有了足夠的人手之後,你就會被不斷增長的bug列表淹沒。假設有100人的團隊,工程師非常優秀,每個月只產生一個bug。總共來講,這組優秀的工程師每天還是能產生5個bug。更糟糕的是,在一個複雜系統,當工程師改造已知的bug,在附近寫程式的時候,修復一個bug經常導致另外一個bug。

 

最好的團隊會找到方法,把成員的集體智慧轉換成整個團隊的好處。那就是自動化測試工作完成的事。團隊的一個工程師寫完一個測試之後,就新增到一個其他人也能使用的公共資源池裡。團隊其他人現在就能執行測試並且在它能發現問題的時候能夠受益。可以和基於除錯的方法比較一下,這種方法每次一個bug出現的時候,工程師必須付出使用偵錯程式深入除錯的代價。這種工程師資源的代價是夜以繼日的,這也是GWS反其道而行之的根本原因。

以現代化開發的速度進行測試

軟體系統正在變得越來越大,並且前所未有的複雜。谷歌一個典型的應用或服務一般都有數萬行程式碼。這個應用或服務會使用數以百計的庫或者框架,並且需要通過不可靠的網路被交付到不斷增長的各種平臺之上,平臺上有不可計數的配置確保能夠執行。更加糟糕的是,新的版本會頻繁地推送給使用者,有時是每天很多次。每年一次到兩次的更新,軟體非常瘦身的時代已經過去了。

 

人類手動地驗證系統的每一個行為的能力,已經不能更上大部分軟體中需求和平臺爆發的節奏。想象一下全部手工測試谷歌搜尋的所有功能吧,比如查詢航班,電影時刻表,相關的圖片,以及網頁查詢結果(見圖11-1,圖片略)。即使你明白怎麼解決這個問題,但是你依然需要把這個工作量乘以谷歌搜尋支援的語言、國家和裝置,並且別忘了檢查可訪問性和安全性。嘗試讓人們手動和每個不會擴充套件的功能互動,進而評估產品質量。當說到測試的時候,有一個清晰的答案:自動化。

編寫、執行、響應

最純粹的形式上來看,自動化測試包含三個活動:編寫測試,執行測試,響應測試失敗。一個自動化的測試就是一小段程式碼,通常是一個函式或方法,呼叫到一個你要測試的更大系統一個獨立的部分。測試程式碼配置好預期的環境,呼叫到系統,通常是已知的輸入,並驗證結果。有些測試非常小,只用一個簡單的程式碼路徑;其他的就會更大,可能包含整個系統,例如一個移動作業系統或網頁瀏覽器。

 

例子11-1 展示了一個極其簡單的Java測試,沒有使用任何框架或者測試庫。這不是你寫整個測試集的方法,但是根本上來說自動化測試和這個簡單的例子差不多。

 

 

在以前的流程裡,專職的軟體測試員盯著一個系統的新版本,測試每個可能的行為,和以前的QA流程不一樣,現在構建系統的工程師在為自己的程式碼編寫和執行自動化測試中發揮更為主動和整合的作用。即使是在QA部門很重要的公司裡,開發自己寫的測試也是司空見慣的。以目前系統開發的速度和規模來看,唯一能跟上的方法是在所有工程師成員中共享測試的開發工作。

 

當然,編寫測試和編寫好的測試是不一樣的。培訓數萬名工程師寫好的測試是非常困難的。在接下來的章節,我們會討論我們關於編寫好的測試學到的知識。

 

編寫測試知識自動化測試流程的第一步。你寫完測試之後,你需要執行。頻繁地執行。自動化測試內部包含一遍一遍重複相同的動作,只有當有些測試失敗之後才會需要人們的注意。我們會在第23章討論持續整合(CI)和測試。通過把測試變成程式碼,而不是一系列手工的步驟,我們可以在每次程式碼變更的時候執行它們——每天可以輕鬆執行幾千次。不像人類測試員,機器從來不會覺得疲倦或乏味。

 

用程式碼編寫測試的另一個好處,是很容易為其在不同的環境執行模組化。測試Firefox中的Gmail行為,需要的工作並不比Chrome更多,前提是你有這兩種系統的配置。日語介面或德語介面的測試可以用測試英語介面的程式碼完成。

 

處於活躍開發階段的產品或服務不可避免地經歷測試失敗。衡量一個測試過程是否有效的方法就是看它怎麼處理測試失敗。允許失敗的測試積累成堆,會讓他們提供的價值黯然失色,所以無論如何也不能讓這件事出現。在測試失敗的數分鐘內修復測試這件事,團隊如果能夠作為高優處理,就會保持高的信心,並且把失敗快速隔離,因此能夠產生更多的價值。

 

總之,一個健康的自動化測試文化鼓勵每個人分享編寫測試的工作。這樣一種文化也確保測試能夠經常執行。最後,並且也許是最重要的,快速修復失敗的測試是非常重要的,只有這樣才能在整個流程中保持較高的信心。

測試程式碼的好處

對於一些從沒有足夠測試文化組織出來的開發者,編寫測試能夠提升生產力和速率的想法可能正好相反。畢竟,編寫測試的行為需要花費和實現一個需求的成本一樣高(如果不是更高的話)。正相反,在谷歌,我們發現在軟體測試上的投入對於開發者的生產力提供了幾個關鍵好處:

 

更少的除錯

和預期一樣,測試過的程式碼提交的時候缺陷更少。關鍵的是,在整個生存週期之內,缺陷都更少;大部分缺陷都會在程式碼提交之前被捕獲。谷歌的程式碼片段在其生命週期之內一般會被編輯幾十次。程式碼會被其他團隊或者自動化維護系統所改變。一次寫好的測試在專案的整個生命週期會持續帶來紅利,並且防止高成本的缺陷和煩人的除錯過程。專案的變化,或者專案的依賴,如果導致測試失敗,會被測試基礎設計檢測到,並在問題釋出到生產之前回滾。

 

變更中提升的自信

所有的軟體都會變更。有完備測試的團隊可以滿懷信心地審查和接受專案的變更,因為專案所有重要的行為會被持續驗證。這樣的專案鼓勵重構。保持現有行為重構程式碼的變化對於現有的測試應該(理論上)不需要改變。

 

文件質量提升

軟體文件的不可靠性臭名昭著。從過期的需求到丟失的邊界用例,文件常常和程式碼的關係非常脆弱。顯然,一次針對一個行為的測試集可以看成可執行的文件。如果你想知道程式碼在某個特定用例下做什麼,看一下這個用例的程式碼就好了。更好的情況是,當需求變化的時候,新的程式碼破壞一個存在的測試,我們就收到了一個清晰的訊號:“文件”現在過時了。注意,只有測試在清晰和簡潔地情況下才能作為文件存在。

 

更簡單的審查

谷歌所有的程式碼在提交之前都會被至少一個其他的工程師審查(參見第9章)。如果程式碼審查中包含完整的測試能夠說明程式碼的正確性、邊界用例和錯誤條件,程式碼審查者就會花更少的時間驗證程式碼是否符合預期。不用繁瑣的大腦檢查程式碼的每個用例,而是驗證每個用例是否有一個通過的測試。

 

深入思考的設計

為新程式碼編寫測試是一個測試程式碼本身API設計的實用的手段。如果新程式碼難以測試,常見的原因是被測程式碼有太多職責,或者難以管理的依賴。設計良好的程式碼應該是模組化的,避免緊耦合的且聚焦於特定職責的。更早修復設計問題常常意味著後期更少的返工。

 

更快、更高質量的釋出

有一個健康的自動化測試集,團隊就能有信心地釋出新版本。谷歌的很多專案每天都發佈一個新版本——甚至是有數百名工程師和每天數千次程式碼提交的大專案。沒有自動化測試是不可能的。