當前位置:
首頁 > 知識 > 回顧python中的單元測試和模擬

回顧python中的單元測試和模擬

回顧python中的單元測試和模擬

Python部落(python.freelycode.com)組織翻譯,禁止轉載,歡迎轉發。

我之前的博客文章:Python Mocking 101: Fake It Before You Make It,討論了python中模擬和單元測試的基礎機制。這篇文章涵蓋了在我過去一年半的Python測試經驗中證明有效的更高級的軟體工程原理。特別地,我想重新思考patching單元測試中的mock對象。

patching外部客戶端

本篇文章中的客戶端指的是任何產生副作用的對象,副作用例如磁碟寫入或者網路I/O。想像一個類,CloudCreator,從HTTP接受信息,產生了一些副作用,如創建了一些雲基礎設施,並通過HTTP在響應中發送信息:

回顧python中的單元測試和模擬

我們可以像下面這樣測試CloudCreator:

回顧python中的單元測試和模擬

patch給了我們測試我們的CloudCreator類而不產生任何網路副作用的能力。但是這種設計有一些缺陷。如果CloudCreator使用了大量外部客戶端,我們需要堆棧大量patch調用。此外,CloudCreator和它的單元測試極其依賴於HTTPClient;這使得更改網路客戶端變得困難。

Fugue工程師Josh Einhorn指出patch的一些其他缺點:

  • 使用它意味著在類中某處存在隱式依賴關係,而另一位開發人員不知道這一點。構造函數args使依賴顯式。

  • 當重構底層實現時,使用patch會需要更新多個不相關的單元測試,而且由於patch使用硬編碼字元串而不是更多強類型引用(可以由linters/IDEs識別),我們並不總能弄清哪些單元測試需要更改。

  • 使用patch是一個代碼異味,因為這意味著被測試的類已經被耦合到一個或多個具體的類。

  • 依賴於patch來進行單元測試的代碼不能移植到其他語言。帶有編譯器的靜態類型語言不會允許猴子補丁(不嚴肅的運行)。這種結構的重構需要正確的單元測試,例如一個另一種語言的類。

大體上,如果測試一個類需要大量patching外部客戶端,這就是需要重構的標誌。有經驗的軟體工程師將會知道這個例子是依賴反轉的主要機會。

依賴反轉和注入

在這種情況下,依賴反轉,特別是依賴注入在軟體工程領域中是老生常談的主題。對於外行來說,依賴注入是這樣的一個概念:一個類或函數應該被給與它所依賴的外部客戶端,而不是自己去創建它。這使得代碼可以在多個上下關係中運行,具體取決於它被給予的是什麼客戶端。

在我們的例子中,CloudCreator創建雲基礎設施的核心功能不依賴於任何發送和接受消息的特定方式。因此,用這樣的方式來寫類是合乎邏輯的:網路 I/O由在運行時注入的客戶端處理,而不是硬編碼(該代碼使用Python的type-hinting語法):

回顧python中的單元測試和模擬

該封裝允許類與HTTP客戶端,TCP/IP客戶端,ZMQ客戶端,或者SQS/SNS客戶端一起使用,只要網路I/O符合NetworkClient中定義的預設介面,例如NetworkClient.recv和NetworkClient.send(data)。這是一個很簡化的處理,精明的觀察者會注意到,客戶端的介面規範會變得至關重要,但那就是另一篇文章要說的了。

依賴注入的一個主要優點是,它允許開發者在單元測試時輕鬆傳入mock對象。現在我們可以將我們的測試升級成這樣:

回顧python中的單元測試和模擬

我們創建一個mock網路客戶端來進行單元測試,使用MagicMock的autospec參數來創建一個遵守NetworkClient介面的mock對象。

在這個簡單例子中,patch允許我們創建一個更死板的單元測試來遮蓋一個死板的設計。正如我之前所說,如果你patching了不只幾個調用,那這是你需要重構代碼的標誌了。請注意,patch依然有用,例如patch time.time調用或者其他無副作用庫調用。

更多依賴注入

依賴注入非常有用,但當我們的類需要大量的客戶端時我們該怎麼辦呢?我們可以添加更多的參數並分別注入他們:

回顧python中的單元測試和模擬

通過添加默認值,可以選擇性地初始化客戶端,這使得單元測試更加容易(這部分稍後再說)。但是,這種形式依舊是很笨重的,特別是在進行集成測試的時候。考慮一下,如果我們更改了我們的消息處理程序,並且之後想要測試它與服務端的通信是否正確,這會發生什麼情況。初始化CloudCreator需要大量繁瑣的工作來創建和初始化客戶端對象。Python的優勢之一就是其互動式解釋器,它能夠進行迭代開發過程,並保留了輕鬆使用REPL的能力,這使得開發者輕鬆了許多。要求開發人員創建並初始化十數個客戶端對象,然後才能測試核心類的一個小更改,這會產生不小的挫敗感。

一個解決方法是將創建客戶端封裝到一個單獨的對象中。我們甚至可以在構造函數中寫入關於操作順序和依賴關係的信息:

回顧python中的單元測試和模擬

請注意,CloudCreator仍然通過對所需服務的顯式調用來進行初始化。這使得未來的開發人員輕鬆的了解CloudCreator需要什麼服務。這使CloudCreator的構造函數只需要一個CloudCreatorServices對象就夠了:

回顧python中的單元測試和模擬

然而,這將CloudCreator和CloudCreatorServices的具體實現聯繫起來,正好是CloudCreator需要的服務。如果將CloudCreator推廣到為多個類創建服務,則調用者必須假定每一個單獨的服務都是被使用推廣的Service類的類需要的。

不幸的是,這個天真的實現失去了原始依賴注入的靈活性。這很容易補救:

回顧python中的單元測試和模擬

在這一點上,所有我們所做的都是移動複雜的部分。開發者依舊負責初始化所有客戶端。我們沒有提供任何工具使他們工作得輕鬆一點。我們可以通過在CloudCreatorServices中建立複雜的初始化程序來做到這一點:

回顧python中的單元測試和模擬

現在我們把客戶端的創建隱藏到這些初始化方法中了。這看起來是一個好的解決方法,但如果我們看得更仔細一點,這依然有一個缺點。當CloudCreatorServices被初始化的時候,我們創建了每一個客戶端,即使我們知道我們將不會使用它。如果其中一個客戶端服務失常並超時,但我們仍然想要測試其他功能時,我們該怎麼辦呢?有更靈活的辦法嗎?

我們可以使用getter方法來改變初始化不變數的順序:

回顧python中的單元測試和模擬

這個解決方案讓我們可以延遲載入,所以客戶端只按需要初始化,同時保持根據需要交換客戶端的能力。但是,getter方法並不非常pythonic。有沒有一個語言特性我們可以用來找到更Pythonic的方法呢?

@property

我們要找的這個特性就是@property:

回顧python中的單元測試和模擬

這看起來跟我們之前使用getter方法的解決方案很像,但我們去掉了get_,加入了@property裝飾器。@property將getter方法轉換成屬性。作為一個屬性就可以直接訪問,就像CloudCreatorServices.database_client,不需要括弧。此外,通過使用@<property_name>.setter將setter函數裝飾成一個屬性,可以讓我們在將來添加一個setter。例如:

回顧python中的單元測試和模擬

當我們給database_client分配一個值時,setter將被透明地調用:

回顧python中的單元測試和模擬

使用@property可以保留直接訪問實例屬性的Python標準,同時給了我們在getter和setter中包含屬性訪問方法的靈活性。

通過依賴注入進行mocking

依賴反轉的一個優點是使得單元測試更加簡單。回想一下,CloudCreator的初始化參數具有默認值,這使我們可以選擇性地模擬客戶端服務對象來進行特殊測試:

回顧python中的單元測試和模擬

由於其他客戶端服務沒有被初始化,很簡單可以判斷出網路代碼路徑是否觸及其範圍之外的對象,這常常是一個某些地方不正常的信號。當然,全面的單元測試需要mock每一個服務,但這些都很容易添加。

通過使用依賴反轉,我們淘汰了在單元測試中patch的需要,同時為開發者提供了一個強大的,節省時間的集成測試工具。


英文原文:https://blog.fugue.co/2017-07-18-revisiting-unit-testing-and-mocking-in-python.html
譯者:南北丶

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python部落 的精彩文章:

Python機器學習和深度學習:你需要知道的內容
使用Matplotlib的窗口部件進行數據探索
Pychrome:能跟chrome開發者工具交流的Python包
40行Python代碼實現一個簡單的演化過程

TAG:Python部落 |

您可能感興趣

說說Python中的單元測試
使用 PyHamcrest 執行健壯的單元測試
用 jest 單元測試改善老舊的 Backbone.js 項目
Kanye West 將攜手 Jaden Smith 製作《Omniverse》單元劇
SpringBoot | 第十三章:測試相關 ( 單元測試、性能測試 )
Massdrop/Nuforce EDC3 三單元動鐵入耳式耳機測評報告Soomal
Massdrop/Nuforce EDC3 三單元動鐵入耳式耳機測評報告「Soomal」
Massdrop/Nuforce EDC3 三單元動鐵入耳式耳機 圖集「Soomal」
In-Ear Prophile-8 PP-8S 八單元動鐵入耳式耳機測評報告「Soomal」
In-Ear Prophile-8 PP-8S 八單元動鐵入耳式耳機 圖集「Soomal」
DeepMind重磅:神經算術邏輯單元,Keras實現
其力 qdc 海王星 Neptune 一單元動鐵入耳式耳機測評報告 [Soomal]
氣動中音單元的Gauder Acrona 200落地音箱
耳機丨「十單元頂配,美系旗艦新作」Noble Audio Kaiser Encore耳機
千元級神器,試聽OpenAudiO雙單元耳機Khat
Nuforce HEM8 四單元動鐵入耳式耳機測評報告「Soomal」
《Rick and Morty》釋出人氣單元「Pickle Rick」刪減片段
魅族 Meizu Live 四單元動鐵入耳式耳機測評報告 [Soomal]
其力 qdc 海王星 Neptune 一單元動鐵入耳式耳機 圖集 [Soomal]
Nuforce HEM8 三單元動鐵入耳式耳機 圖集「Soomal」