.NET Core TDD 前傳: 編寫易於測試的代碼 -- 全局狀態

第1篇: 講述了如何創造"縫".  "縫"(seam)是須要知道的概念.html

第2篇, 避免在構建對象時寫出不易測試的代碼.ide

第3篇, 依賴項和迪米特法則.單元測試

本文是第4篇, 將介紹全局狀態引發的問題.測試

 

全局狀態

全局狀態, 也能夠叫作應用程序狀態, 它是一組變量, 這些變量維護着應用程序的高級狀態.ui

在程序裏, 全局狀態可能都存放在一個全局狀態對象裏, 例如ASP.NET裏面的HttpContext; 或者它們多是全局的變量, 這些全局變量在程序的任何地方均可以訪問.spa

不論是如何實現的全局狀態, 每一個全局狀態變量在內存裏只有一個實例. 因此若是一個類裏更新了全局變量的值, 那麼另外一個類訪問該變量的時候它的值就是剛纔被更新的值.code

有些狀況下, 使用全局狀態確實有用; 可是若是使用不當, 則會對測試形成很大的影響.htm

 

全局狀態對測試引發的問題

  • 使用靜態方法或全局變量訪問全局狀態的時候, 就引發了對全局狀態的直接耦合. 這很很差.
  • 這種耦合就致使很難對測試進行設置. 針對每一個測試, 咱們必須建立和設置好存儲全局狀態的對象. 或者把全局變量設定爲所需的值.
  • 由於每一個全局狀態變量在內存裏只有一個實例, 那麼咱們就沒法進行並行單元測試了. 若是咱們爲A測試設定了全局變量的值, 而後在測試A結束前開始測試B, 這時測試B修改了全局變量的值, 這時測試A就可能會失敗, 由於它所期待的全局變量不是這個值.
  • 上面的這種現象就叫作鬼魅般的超距做用(Spooky Action at a Distance). 而實際項目中確實常常發生這樣的狀況, 並行跑單元測試的時候偶爾會失敗, 而單獨去跑失敗的測試時卻一直成功. 這種耦合到全局狀態的測試就不能再稱爲隔離測試了.

 

危險信號

  • 全局變量
  • 調用靜態字段或調用擁有靜態字段的類的靜態方法. 但也僅限於該類的靜態方法使用了該類的靜態字段. 
  • 單例模式 (Singleton Pattern)
  • 單元測試會隨機的失敗, 可是又沒發現明確的緣由.

 

解決辦法

  • 儘可能使用本地(局部, 越窄越好)狀態變量
  • 若是第三方庫使用了靜態方法, 那麼應該使用一個包裝類來對該方法進行包裝. 這個包裝類仍是要實現一個接口. 用它的時候注入該接口便可. 這樣測試的時候就能夠爲包裝類建立測試替身了, 並把全局狀態解耦.
  • 使用可依賴注入(IoC/DI)的單例體, 這種單例體是由IoC容器建立的.

 

例子

就舉一個例子吧.對象

有這樣一個獲取當前登陸用戶權限的類, 它使用的是單例模式:blog

這個是典型的單例模式, 它會保證在程序中只返回一個實例, 這裏就很少介紹了.

 

下面這個Service會調用上面這個Auth類:

Auth是單例模式的, 並且還調用了靜態方法.

如今的狀態是, OfficeService和Auth所包含的全局狀態緊密的耦合到了一塊兒. 

 

如何解決問題

首先應該把單例模式去掉, Auth類只保留兩個屬性和一個方法:

 

而後在service裏面應該注入IAuth接口並使用:

 

那麼接下來就須要保證這個IAuth不管在程序中注入了多少次, 都是同一個實例.

這時就須要使用依賴注入(DI) 庫了. 如今的DI庫一般容許指定IoC容器中每對綁定服務的做用範圍(Scope), 或叫作生命週期管理.

例如ASP.NET Core內置的IoC容器就內置了這種功能. 在ASP.NET Core 項目的Startup類裏, 這樣寫就能夠保證每次請求IAuth的時候只會獲得同一個對象實例:

如今這個"單例"的工做是由IoC容器來負責了. 在其它地方正常的注入IAuth使用便可.

 

先寫到這, 本文的概念性內容和更多的例子請參考Angular創始的人這篇文章: http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/

相關文章
相關標籤/搜索