關於單元測試體系結構的一些心得

2020-12-23 三微授漁

自動化測試是任何大型軟體項目不可或缺的一部分,可作為提高質量,生產率和靈活性的一種手段。因此,至關重要的是,系統架構的設計必須能夠促進自動化測試的開發和執行。

質量得到提高,因為自動化測試的執行可以讓我們找到,並在開發周期的早期解決問題,很多之前產品變更部署到生產和可用給最終用戶。

生產率提高是因為在開發周期中發現問題的時間越早,修復該問題的成本越低,並且不難理解為什麼。如果軟體開發人員能夠在將代碼更改集成到主存儲庫之前運行自動化測試套件,則他可以快速發現新引入的錯誤並將其修復。但是,如果沒有這樣的測試套件,則新引入的錯誤可能只會在以後由最終用戶報告的手動測試階段中出現,甚至更糟,這要求開發人員退出常規開發工作流程以進行調查和修復。

靈活性得到了改善,因為開發人員在依賴於測試覆蓋率較高的測試套件來評估其代碼更改的影響時,對重構代碼,升級程序包以及在需要時修改系統行為更有信心。

在討論自動化測試時,我也喜歡將風險管理的話題引入對話中。作為首席軟體工程師,風險管理是我工作的重要組成部分,它涉及對開發團隊進行實踐和流程指導,以減少產品技術退化的風險。從上面列出的好處中可以明顯看出,採用適當的自動化測試策略很合適,可以幫助減輕軟體項目中的風險。

展望未來,我們可以根據實現和運行自動測試的策略將自動測試分為至少三種不同類型,如以下著名的測試金字塔所示:

就使用的時間和資源而言,單元測試的開發成本低且運行成本低,並且單元測試專注於測試與外部依賴項隔離的單個系統組件(例如,業務邏輯)。

集成測試向前邁了一步,並且在不隔離外部依賴關係的情況下進行了開發和運行。在這種情況下,我們有興趣評估所有系統組件在組合在一起並面臨集成約束(例如:聯網,存儲,處理等)時是否按預期進行交互。

最後,在金字塔的頂端,圖形用戶界面測試是自動化和執行最昂貴的。他們通常依靠UI輸入/輸出腳本和回放工具來模仿最終用戶與系統圖形用戶界面的交互。

在本文中,我們將重點介紹測試金字塔的基礎,單元測試以及促進採用它們的系統體系結構注意事項。

有效單元測試的屬性

讓我們列舉一下什麼是有效的,精心設計的單元測試。以下是一個命題:

簡短,只有一個目的

簡單,清晰的設置和拆卸

快速,只需幾分之一秒即可執行

標準化,遵循嚴格的約定

理想情況下,單元測試應顯示所有這些屬性,下面我詳細說明原因。

如果單元測試不夠短,將很難閱讀並理解其目的,即確切地說是測試的內容。因此,出於這個原因,單元測試應該有一個明確的目標,並且只評估一件事,而不是嘗試同時執行多個評估。這樣,當單元測試失敗時,開發人員將更加輕鬆快捷地評估情況並進行修復。

如果單元測試需要大量的精力來設置他們的測試環境,然後將其拆除,則開發人員通常會開始質疑,花費在編寫這些測試上的時間是否值得。因此,我們需要提供一個編寫單元測試的環境,該環境要管理測試上下文的所有複雜性,例如注入依賴關係,預加載數據,清除緩存等。編寫單元測試越容易,開發人員創建它們的動力就越大!

如果執行一組單元測試要花費大量時間,則開發人員自然會減少執行頻率。這裡的危險在於擁有如此冗長的單元測試套件,以至於變得不切實際,開發人員開始跳過運行它或有選擇地運行它,從而降低了其有效性。

最後,如果測試未標準化,不久後您的測試套件將開始看起來像狂野的西部,編寫單元測試所使用的編碼風格有時會有所不同,有時會發生衝突。因此,在整個單元測試範圍內,追求系統設計的一致性對於整個系統來說同樣有效。

一旦我們對有效的單元測試的構成達成共識,就可以開始定義提升其性能的系統架構準則,如以下各節所述。

軟體複雜度

除其他因素外,軟體複雜性還源於系統中組件之間不斷增長的交互次數以及內部狀態的演變。隨著複雜度的提高,無意識地幹擾複雜的組件交互網絡的風險也隨之增加,有可能導致在更改代碼時引入缺陷。

此外,根據常識,系統的複雜性越高,維護和測試它就越困難,這導致了第一個(一般)準則:

密切關注軟體的複雜性並遵循設計實踐來控制它

在管理複雜性同時提高可測試性時,值得一提的做法是在系統設計中儘可能採用「 純函數」和「 不變性」。純函數是具有以下屬性的函數:1

對於相同的參數,其返回值是相同的(局部靜態變量,非局部變量,可變參考變量或來自I / O設備的輸入流無變化)。

它的評估沒有副作用(本地靜態變量,非本地變量,可變引用參數或I / O流不會發生突變)。

從其特性可以明顯看出,純函數非常適合於單元測試。它們的用法也消除了對許多補充性實踐的需求,這些補充性實踐將在以下各節中討論,以處理大多數有狀態的組件。

不變性同樣重要。不可變對象是創建後狀態無法更改的對象。它們更易於交互且更可預測,從而有助於降低系統複雜性,消除全局狀態。

隔離依賴

按照它們的定義,單元測試旨在隔離地測試各個系統組件,因為我們不希望組件的單元測試的結果受到其依賴項之一的影響。隔離程度會根據被測組件的具體情況以及每個開發團隊的偏好而有所不同。我個人不擔心隔離輕量級的內部業務類,因為我發現用測試目標組件替代它們並沒有增加任何價值,該組件將顯示幾乎相同的行為。儘管如此,這裡的策略可能很簡單:

在組件設計中應用依賴項反轉模式

依賴關係反轉模式(DIP)指出,高級對象和低級對象都應依賴抽象(例如接口),而不是特定的具體實現。一旦將系統組件從其依賴關係中分離出來,我們就可以在單元測試的上下文中通過簡化的,針對測試的具體實現輕鬆地替換它們。下面的類圖說明了結果結構:

在此示例中,被測組件依賴於資料庫和文件存儲抽象。當部署到生產環境中時,我們可能會為存儲庫類注入基於SQL的具體實現,並為文件存儲組件注入基於S3的實現,以便在AWS Cloud中遠程存儲文件。但是,在運行單元測試時,我們將希望注入不依賴外部服務的簡化功能實現,例如以綠色繪製的「內存中」實現。

如果您不熟悉DIP,那麼我還會發表另一篇文章,內容是有關如何在可能會有所幫助的相似上下文中使用DIP的實用概述:集成第三方模塊。

假裝與假貨辯論

請注意,我並不是將這些「內存中」實現稱為「模仿」,它們是模擬對象,它們以有限的受控方式模仿了真實對象的行為。我故意這樣做,因為我反對使用模擬對象,而建議使用完全兼容的「偽」實現,這為我們提供了編寫單元測試的更大靈活性,並且可以比設置更可靠的方式在多個單元測試類中重用模擬。

為了更詳細地說明,假設我們正在編寫一個依賴於組件的單元測試。 文件存儲抽象。在此測試中,組件將一個項目添加到文件存儲中,但實際上並不擔心操作是成功還是失敗(例如,日誌文件),因此,我們決定以「虛擬」方式模擬該操作。現在,假設稍後需求發生變化,並且組件需要確保在繼續操作之前通過從文件存儲中讀取文件來創建文件,這迫使我們更新模擬的行為以使測試通過。然後,想像需求再次發生變化,並且組件需要寫入多個文件(例如:每個日誌級別一個),而不是僅寫入一個,從而迫使我們的模擬對象行為得到另一種改善。你知道發生了什麼嗎?我們正在逐步改進我們的模擬,使其更類似於具體的實現。更糟糕的是,我們最終可能會遇到許多獨立的,半生不熟的,

為了解決這種情況,我提出以下準則:

依靠Fakes而不是Mocks來實施單元測試,將它們視為一流的公民,並將其組織為可重用的模塊

由於Fake組件實現了商業行為,因此與設置模擬相比,它們本質上是更昂貴的初始投資。但是,它們的長期回報肯定更高,並且更符合有效的單元測試的特性。

編碼風格

每個自動化測試都可以描述為三步腳本:

1、準備測試環境

2、執行按鍵操作

3、驗證結果

這是合乎邏輯的考慮,給出一個初始已知狀態,當執行一個操作,那麼就應該產生相同的預期結果,每一次。為了使結果變得不同,必須更改初始狀態,或者更改操作實現本身。

您可能對上面用黑體字標出的單詞很熟悉。如果不是這樣,它們代表了一種流行的Given-When-Then模式,以一種有利於可讀性和結構的方式編寫單元測試。這裡的想法很簡單:

為編寫單元測試定義並實施單一的標準化編碼樣式

可以使用多種方式採用「當時給定」模式。其中之一是將單元測試方法構造為三種不同的方法。例如,考慮密碼強度測試:

使用這種方法,主要的測試方法變成了對單元測試目的的三行描述,即使是非開發人員也可以通過閱讀它輕鬆地理解。在實踐中,單元測試的主要方法最終成為系統行為的低級文檔,不僅提供文本描述,還提供執行代碼,調試代碼並找出內部情況的可能性。當新開發人員加入團隊時,這對於縮短系統架構學習曲線非常有價值。

重要的是要強調,在編碼風格方面,沒有唯一正確的方法。我在上面提供的示例可能會使某些開發人員感到不滿,例如,因為冗長而令人不悅,這沒關係。真正重要的是,在您的開發團隊中就編碼慣例達成協議,以編寫對您有意義並堅持下去的單元測試。

管理測試環境

單元測試上下文管理是一個討論不夠多的主題。「測試上下文」是指成功運行單元測試所需的整個依賴項注入和初始狀態設置。

如前所述,當開發人員花費較少的時間來擔心設置測試上下文並花更多時間編寫測試用例時,單元測試會更有效。我們從以下觀察得出我們的最後一個準則,即大量測試案例可以共享一些測試上下文:

利用構建器類將測試上下文的構建與單元測試用例的實現分開

這個想法是將測試上下文的構造邏輯封裝在構建器類中,並在單元測試類中引用它們。然後,每個上下文構建器負責創建特定的測試方案,並可選地定義用於使其特定化的方法。

讓我們看一下另一個說明性的代碼示例。假設我們正在開發一個反欺詐組件,用於檢測行動應用程式用戶的可疑位置變化。測試上下文生成器可能如下所示:

由此創建的測試上下文MobileUserContextBuilder足夠通用,因此從應用程式已經註冊了移動用戶的狀態開始所需的任何測試用例都可以使用它。最重要的是,它定義了AddDevice具體化測試環境以適應我們虛擬的反欺詐組件測試需求的方法。

考慮調用此反欺詐組件GeolocationScreener,它負責檢查移動用戶的位置是否更改得太快,這表明他可能是在偽造自己的真實坐標。其單元測試之一可能如下所示:

可見,在此示例測試類中專用於設置測試上下文的代碼量很小,因為它幾乎完全包含在builder類中,從而保留了代碼的可讀性和組織性。隨著越來越多的測試用例利用可用的測試上下文構建器庫,設置測試上下文所需的攤銷時間變得非常短。

結論

在本文中,我討論了單元測試的主題,提供了五個主要指南,以應對在不斷增長的測試用例中保持有效性的挑戰。這些準則對系統體系結構有重要影響,從軟體項目開始就應該考慮單元測試要求,以促進開發人員在其中看到價值並有動力編寫單元測試的環境。

單元測試應被視為系統體系結構的組成部分,與它們所測試的組件一樣重要,而不應被視為開發團隊僅出於填寫管理報告複選框或提供指標而編寫的二等公民。

最後,如果您正在使用很少或沒有單元測試的遺留項目中,而沒有使用DIP,則由於您有意避免談論複雜的模擬框架,因此本篇文章可能沒有為您提供最佳策略。遺留項目的上下文成為將單元測試引入極度耦合的代碼的可行選擇。

相關焦點

  • 關於Android單元測試,你需要知道的一切
    原文:http://www.jianshu.com/p/dc30338a3e8作者:小創本文是小創同學這幾個月來關於
  • TDD測試驅動開發的實踐心得
    而且筆者關於TDD的一些細節,可能也與Robert C.Martin的看法並不一致,這一點後續筆者會再在專門闡述TDD的文章中再來說明。但整體上筆者對TDD是深信不疑的。但有幸的是,過去兩年,分別在19年公司的一個項目及20年自己的一個業餘項目中嘗試完整的應用了TDD的做法,所以也基於此得出了一些心得。也堅定了自己對TDD的信念。
  • 單元測試常用的方法
    4.關於樁代碼   我們認為,單元測試應避免編寫樁代碼。樁代碼就是用來代替某些代碼的代碼,例如,產品函數或測試函數調用了一個未編寫的函數,可以編寫樁函數來代替該被調用的函數,樁代碼也用於實現測試隔離。
  • 解讀Android官方MVP項目單元測試
    提起Android單元測試,大多數同學都很感興趣,都認可其重要性,但在實際工作中真正應用的非常少,一方面是各種推廣落地的阻力,比如擔心對開發進度與效率的影響;另一方面也是這方面的官方指引比較少,雖有一些熱門方案,但一直難有真正意義上的最佳實踐。
  • 系統測試:單元測試相關知識筆記
    一、單元測試概念單元測試也成為模塊測試,在模塊編寫完成且五編譯錯誤後就可以進行。單元測試側重模塊中的內部處理邏輯和數據結構。如果採用機器測試,一般用白盒測試法。二、單元測試檢查模塊特徵1、模塊接口模塊接口保證了測試模塊數據流可以正確地流入、流出。主要檢查一下要點:測試模塊輸入參數和形式參數在個數、屬性、單位是否一致。
  • 基於OBE的數據結構課程考核評價體系設計與實踐
    這就需要教育工作者對OBE課程教學評價進行實踐研究,設計能夠幫助學生達成畢業要求的教學方式和考核評價體系,滿足對學生基本知識、工程能力和專業素養的綜合考量。數據結構是計算機科學與技術專業重要的基礎學科,學生只有熟練掌握這一學科才能更好地學習程序的研發、設計,為其他課程的系統學習奠定基礎。
  • 單元測試的藝術
    以前也讀過關於單元測試、測試驅動開發的書,也採用了相關的方法實踐了一段時間,但就是因為缺乏對測試的系統了解,所以一開始著急著快速編碼完成任務,就把測試放在一邊了
  • 如何對機器學習做單元測試
    我在谷歌Brain學到的一個主要原則是,單元測試可以決定算法的成敗,可以為你節省數周的調試和訓練時間。然而,在如何為神經網絡代碼編寫單元測試方面,似乎沒有一個可靠的在線教程。即使是像OpenAI這樣的地方,也只是通過盯著他們代碼的每一行,並試著思考為什麼它會導致bug來發現bug的。顯然,我們大多數人都沒有這樣的時間,所以希望本教程能夠幫助你開始理智地測試你的系統!
  • 十年測試老兵教你構建軟體測試知識體系和技能樹(附思維導圖)
    因此,掌握Dokcer和Kubernetes的使用,了解他們背後的原理,一定會讓我們測試工作更加高效。關於Docker容器和Kubernetes容器雲,我並沒有看過紙質版圖書。通過學習數據結構與算法,使得我對Python和Java的一些底層原理也有了更深刻的了解,我的編程能力也得到了進一步的提升。所以,很多知識是相通的、相互輔助的。1.6 架構知識
  • Golang後臺單元測試實踐
    Why單元測試新功能的增加,代碼複雜性的提高,優化代碼的需要,或新技術的出現都會導致重構代碼的需求。在沒有寫單元測試的情況下,對代碼進行大規模修改,是一件不敢想像的事情,因為寫錯的概率實在太大了。而如果原代碼有單元測試,即使修改了代碼單測依然通過,說明沒有破壞程序正確性,一點都不慌!
  • 當Espresso遇見Android單元測試
    如果依賴Android環境,但是沒有UI相關或者UI比較簡單(如點擊按鈕)的單元測試可以使用開源庫Robolectric解決依賴問題,使測試運行在JVM上,而非模擬器上,大大提高測試運行效率。但是如果測試UI相關比較複雜的代碼,又可以如何進行測試呢?
  • Android單元測試實踐
    為什麼要引入單元測試  一般來說我們都不會寫單元測試,為什麼呢?因為要寫多餘的代碼,而且還要進行一些學習,入門有些門檻,所以一般在工程中都不會寫單元測試。那麼為什麼我決定要寫單元測試。  這篇文章看完並不會讓你完全掌握單元測試,但是會給你在單元測試的開始有一個好的指引  大大提高工作效率  單元的概念比較模糊,可以是一個方法,可以是一個時機,但是不是一整套環節,一整套環節那就是集成測試了。為什麼說大大提高了工作效率。
  • 聊聊單元測試
    Z哥認為真是由於沒時間而不寫單元測試的人絕對是少數。況且,導致沒時間很大原因可能就是花了太多時間在處理bug上。所以,很多人沒有把單元測試當作一個「工具」,而把它看作是一種「負擔」。在這種心態下,就算要寫單元測試,也是為了寫而寫。更可怕的是,通過mock工具,還真能給任意代碼寫單元測試。但是這樣的做法其實是「買櫝還珠」,真正的浪費時間。
  • 基於RISC體系結構的8位高速MCU的IP軟核設計
    本文介紹的是基於 RISC體系結構的8位高速MCUIP軟核的設計與實現,採用Verilog HDL自上而下地描述了MCUIP軟核的硬體結構,並驗證了設計的可行性和正確性。在實際硬體電路中,該IP核的運行頻率達到75MHz,可應用於高速控制領域。 系統結構設計 本設計的總線採用了哈佛結構,14位指令字長,8位數據字長,指令集與PIC16F676兼容。
  • Android單元測試與模擬測試詳解
    Android領域中這塊還沒有普及,今天主要聊聊Android中的單元測試與模擬測試及其常用的一些庫。一、測試與基本規範1. 為什麼需要測試?    更加易於維護,能夠在修改代碼後保證功能不被破壞。集成一些工具,規範開發規範,使得代碼更加穩定。2. 測什麼?
  • Python單元測試——深入理解unittest
    單元測試的重要性就不多說了,可惡的是python中有太多的單元測試框架和工具,什麼unittest, testtools, subunit, coverage, testrepository, nose, mox, mock, fixtures, discover,再加上setuptools, distutils等等這些,先不說如何寫單元測試,光是怎麼運行單元測試就有
  • AESA雷達信號處理及體系結構
    而其體系結構的未來發展將超越最初的軍事應用,延伸到地球物理測繪、汽車輔助駕駛、自動車輛、工業機器人和增強現實等領域:實際上,這包括任何需要對大量的傳感器數據進行調理,融合到模型中進行判決的應用。隨著AESA體系結構的擴展,它們將突破雷達信號處理專業應用,延伸到其他應用中。
  • 單元測試可測試程式設計師代碼編寫的正確性,如何使用VS2019測試項目
    單元測試是指編寫代碼來驗證開發者編寫代碼的正確性,一般單元測試也是由開發者完成的,自已開發單元測試代碼來檢查自己編寫代碼的通過性。定義:單元測試是開發人員編寫的、用於檢測在特定條件下目標代碼正確性的代碼,單元測試是代碼級別的測試。
  • Spring Boot 單元測試
    一、 單元測試的概念概念:單元測試(unit testing),是指對軟體中的最小可測試單元進行檢查和驗證。在Java中單元測試的最小單元是類。單元測試是開發者編寫的一小段代碼,用於檢驗被測代碼的一個很小的、很明確的功能是否正確。執行單元測試,就是為了證明這 段代碼的行為和我們期望是否一致。
  • Task12: 單元測試
    11.單元測試本節代碼樣例見code/utest文件夾在日常開發中,我們通常需要針對現有的功能進行單元測試,以驗證開發的正確性。在go標準庫中有一個叫做testing的測試框架,可以進行單元測試,命令是go test xxx。測試文件通常是以xx_test.go命名,放在同一包下面。