在準備將軟體上線到生產環境之前需要進行測試。隨著軟體測試方式日趨成熟,軟體開發團隊的測試也在取代大量手動測試,逐漸實現自動化測試。透過自動化測試,開發團隊可以在短短几分鐘內就瞭解到軟體是否存在問題,而不需要等待幾天的時間。
自動化測試大大地縮短了反饋週期,與敏捷開發、持續整合和DevOps文化密切相關。本文將分為上、下篇來探討如何構建一個高響應、可靠並且可維護的測試組合,無論是針對微服務架構、移動應用程式還是物聯網生態系統。
一、自動化測試的重要性
軟體已經成為我們生活中重要的組成部分。早期,軟體的目的僅僅是提高企業效率,但現在它的作用遠不止於此。許多公司都在努力成為一流的數字化公司。作為使用者,我們每天都在使用各種各樣的軟體,創新的車輪轉動越來越快。
要想跟上創新的腳步,我們必須在保證軟體質量的同時加快交付的速度。持續交付是一種軟體工程手法,透過在短週期內完成軟體產品的交付過程,確保軟體可以穩定、持續地釋出。透過構建流水線自動化測試,自動將其部署到測試和生產環境中。
隨著軟體數量的不斷增加,手動構建、測試和部署很快就會變得不切實際。如果我們不想把大量時間都花在重複性的手動測試上,而無法用於開發正常執行的軟體,那麼自動化測試是前進的必由之路。從構建到測試,從部署到基礎架構,自動化測試是不可獲取的。它能夠提高效率、減少錯誤,並未開發人員釋放更多時間專注於創造性的工作。
傳統的軟體測試通常是手工操作的,包括將應用程式部署到測試環境中,然後執行黑盒測試,如點選使用者介面檢查是否有任何故障。這些測試通常是由測試指令碼指定,以確保測試人員進行一致的檢查。
手動測試的所有變更都是耗時、重複且乏味的。重複是枯燥的,而枯燥容易導致錯誤的出現,還會讓測試人員在一週後產生“另謀高就”的想法。幸好,有一種方法可以解決這種重複性工作:自動化測試。
自動化測試會極大程度地改變軟體開發人員的工作方式。一旦將這些測試自動化,測試人員就不再需要手動執行點選操作來檢查軟體是否仍能正常執行。透過自動化測試,可以輕鬆修改程式碼庫。如果之前在沒有適當測試組合的情況下進行大規模重構,你一定會知道這是多麼可怕的經歷。如何確保在重構過程中避免不小心破壞任何東西?只能一個個手動執行測試用例了。如果能在喝一口咖啡的時間內,在幾秒鐘內知道自己是否進行了大規模修改,那該有多好。這聽起來更有意思。
二、測試金字塔
在《敏捷成功》一書中,Mike Cohn提出了一個重要的概念:測試金字塔。這個概念透過視覺隱喻向我們展示了不同層次的測試。
Mike Cohn獨創的測試金字塔由三層組成(從下到上):
- 單元測試
- 服務測試
- UI測試
然而,一些人對測試金字塔的命名和某些概念提出了批評,從現代觀點來看,測試金字塔過於簡單,這會產生誤導。在實際應用中,測試的層次和比例會因專案的特殊需求而有所不同,因此,我們需要靈活地應用測試金字塔,根據具體情況進行調整和定製,以確保我們能夠全面而有效地測試軟體。
儘管如此,測試金字塔的本質是一種經驗法則,用於構建自己的測試組合。Cohn強調在最初構建測試金字塔時要注意兩點:
- 編寫不同粒度的測試
- 隨著測試級別的提高,應進行的測試數量會減少
堅持金字塔的形狀,以構建一個健康、快速和可維護的測試組合,但不要形成“測試冰淇淋錐”,因為這會導致維護困難且執行時間過長。
我們不必過於拘泥測試金字塔中每層的名稱。實際上,這些名稱可能會帶來一些誤導。例如,“服務測試”是一個難以理解術語,正如Cohn本人曾說的“我觀察到很多開發人員完全忽略了這一層”。在現代的單頁面應用框架(如react、angular、ember.js)中,UI測試顯然不必位於金字塔的最高層,完全可以對UI進行單元測試。
考慮到原始名稱的缺點,根據程式碼庫和團隊討的需要,為測試金字塔每層選擇其他名稱,只要中保持一致即可。
三、注意事項
1、團隊在測試命名上保持統一
討論測試的不同分類總是非常困難的。當提到單元測試時,不同人對其理解存在一定差異。以整合測試為例,有人認為整合測試的覆蓋面非常廣,可以測試整個系統中的多個方面。有些人喜歡稱之為整合測試,有些人則更喜歡稱它們為元件測試,還有些人喜歡稱為服務測試。很多人會爭辯說,這三個術語是完全不同的東西。在這個問題上並沒有絕對的對與錯。迄今為止,軟體開發社群也沒法給出關於測試術語的明確定義。
術語含義本身有模糊性,所以不必過分糾結。無論是叫端到端測試也好、廣域棧測試,還是功能性測試,都沒問題。如果業界能有一些明確定義的術語並統一語言,那是再好不過了。可惜目前尚未到達這一點。此外,在編寫測試時會存在許多細微差別,它們的範圍更像是互相重疊而不是互相獨立的,這使得保持術語的一致性更為困難。
重要的是找到適合團隊的術語,並清楚理解不同類別測試之間的區別。團隊需要在測試命名上保持統一,併為每一類測試明確定義範圍。只要在團隊內部達成一致(甚至在組織內部),就不需要過多關注其他事情了。
2、把測試放在部署流水線上
如果正在實施持續整合或者持續交付的實踐,那麼在每次提交更改時,將使用一個部署流水線來執行自動化測試。這個流水線通常會被分成幾個階段,逐步建立起團隊對將軟體部署到生產環境的信心。在考慮如何在部署流水線中放置不同型別的測試時,需要思考持續交付的核心價值觀之一:快速反饋。
構建流水線的目標是在構建失敗時能夠及時通知測試人員。測試人員肯定不希望等待一小時後才發現最新的改動因為幾個簡單的單元測試而失敗。實際上,如果流水線需要這麼長時間才能給到反饋,測試人員可能早就已經等不及回家了。
為了快速獲得但虧,我們可以將執行快速快的測試放在流水線的較早階段執行,這樣我們可以在幾秒或幾分鐘內得到反饋。反之,將執行時間較長的測試(通常是覆蓋範圍更廣的測試)放到流水線的後期階段執行,以免影響我們從執行速度快的測試中獲取快速反饋的體驗。
部署流水線中不同階段的差異並不是由測試型別決定的,而是取決於測試的執行速度和覆蓋範圍。在這種情況下,將一些覆蓋範圍有限、執行速度快的整合測試與單元測試放在同一個階段是一個合理的決策。我們的目標是更快地獲得反饋,而不是在各種型別的測試之間劃出清晰的界線。
3、避免測試重複
我們已經瞭解了為什麼需要為軟體編寫不同型別的測試,但是這還有一個需要避開的陷阱:金字塔不同層級進行重複測試。編寫和維護測試需要花費時間,而閱讀和理解其他人編寫的測試也是如此,此外執行這些測試也要費時間。
對於產品程式碼,我們應該追求間接性,儘量避免重複。在實現測試金字塔時,我們應該牢記以下兩個基本法則:
- 當一個更高階的測試發現了一個錯誤,並且底層測試都透過了,我們應該寫一個低層級的測試來覆蓋這個錯誤。
- 我們應該儘可能地將測試推進到金字塔的下層。
第一條法則是因為低層級測試能夠幫助縮小錯誤範圍,並且將大部分上下文隔離開,從而更容易重新錯誤。在除錯當前問題時,低層級測試能夠更快地執行,而且沒有太多冗餘的內容。此外,它們也是很好的迴歸測試,確保已修復的問題不會再次出現。
第二條法能保持測試組合的快速執行。如果在底層及測試中已經覆蓋了所有情況,那麼維護一個高層級的測試就沒有必要了。因為它並不能為軟體的正常工作提供更多的信心。如果有許多無效的測試,它們只會讓你的日常工作變得繁瑣。這樣的測試組合會拖慢工作節奏,當你改變程式碼行為時,還需要修改更多的測試。
總而言之,如果編寫更高層級的測試可能增加對軟體的信心,那麼就編寫高層級的測試。對於一個Controller類,編寫單元測試可以測試其內部的邏輯。然而,它無法告訴我們該Controller是否能夠真正響應HTTP請求的REST路徑。在這種情況下,我們可以將測試層級上衣,編寫一個專門測試這一點的測試——只測試這一點,不需要更多。我們無需再測試所有的條件分支和邊緣場景,因為底層級測試已經涵蓋了這些內容。確保高層級測試僅關注底層及測試未覆蓋到的部分。這樣可以確保測試的焦點準確,並避免重複勞動。
對待已經失去價值的測試必須堅決將其消滅。我們需要刪掉那些已經被低層級測試覆蓋完全的高層級測試,因為它們不再提供額外的價值。儘可能用低層級測試來取代高層級測試。有時候,這可能會有一些困難,特別是當你知道設計測試本身就很具有挑戰性時。我們要警惕沉沒成本的思維陷阱,果斷摁下刪除鍵。沒有理由在不再提供價值的測試上浪費寶貴時間。
四、寫在最後
不管你是工作在一個微服務專案上,還是IoT裝置上,抑或是手機應用或者網頁應用,希望這篇文章能夠為你提供幫助。下篇,我們將詳細介紹測試金字塔的三個層級。
文章翻譯來源:https://martinfowler.com/articles/practical-test-pyramid.html