.NET Core TDD 前传: 编写易于测试的代码 -- 全局状态
第1篇: 講述了如何創(chuàng)造"縫".? "縫"(seam)是需要知道的概念.
第2篇,?避免在構(gòu)建對象時(shí)寫出不易測試的代碼.
第3篇, 依賴項(xiàng)和迪米特法則.
本文是第4篇, 將介紹全局狀態(tài)引起的問題.
?
全局狀態(tài)
全局狀態(tài), 也可以叫做應(yīng)用程序狀態(tài), 它是一組變量, 這些變量維護(hù)著應(yīng)用程序的高級狀態(tài).
在程序里, 全局狀態(tài)可能都存放在一個(gè)全局狀態(tài)對象里, 例如ASP.NET里面的HttpContext; 或者它們可能是全局的變量, 這些全局變量在程序的任何地方都可以訪問.
不管是如何實(shí)現(xiàn)的全局狀態(tài), 每個(gè)全局狀態(tài)變量在內(nèi)存里只有一個(gè)實(shí)例. 所以如果一個(gè)類里更新了全局變量的值, 那么另一個(gè)類訪問該變量的時(shí)候它的值就是剛才被更新的值.
有些情況下, 使用全局狀態(tài)確實(shí)有用; 但是如果使用不當(dāng), 則會(huì)對測試造成很大的影響.
?
全局狀態(tài)對測試引起的問題
- 使用靜態(tài)方法或全局變量訪問全局狀態(tài)的時(shí)候, 就引起了對全局狀態(tài)的直接耦合. 這很不好.
- 這種耦合就導(dǎo)致很難對測試進(jìn)行設(shè)置. 針對每個(gè)測試, 我們必須創(chuàng)建和設(shè)置好存儲全局狀態(tài)的對象. 或者把全局變量設(shè)定為所需的值.
- 因?yàn)槊總€(gè)全局狀態(tài)變量在內(nèi)存里只有一個(gè)實(shí)例, 那么我們就無法進(jìn)行并行單元測試了. 如果我們?yōu)锳測試設(shè)定了全局變量的值, 然后在測試A結(jié)束前開始測試B, 這時(shí)測試B修改了全局變量的值, 這時(shí)測試A就可能會(huì)失敗, 因?yàn)樗诖娜肿兞坎皇沁@個(gè)值.
- 上面的這種現(xiàn)象就叫做鬼魅般的超距作用(Spooky Action at a Distance). 而實(shí)際項(xiàng)目中確實(shí)經(jīng)常發(fā)生這樣的情況, 并行跑單元測試的時(shí)候偶爾會(huì)失敗, 而單獨(dú)去跑失敗的測試時(shí)卻一直成功. 這種耦合到全局狀態(tài)的測試就不能再稱為隔離測試了.
?
危險(xiǎn)信號
- 全局變量
- 調(diào)用靜態(tài)字段或調(diào)用擁有靜態(tài)字段的類的靜態(tài)方法. 但也僅限于該類的靜態(tài)方法使用了該類的靜態(tài)字段.?
- 單例模式 (Singleton Pattern)
- 單元測試會(huì)隨機(jī)的失敗, 但是又沒發(fā)現(xiàn)明確的原因.
?
解決辦法
- 盡量使用本地(局部, 越窄越好)狀態(tài)變量
- 如果第三方庫使用了靜態(tài)方法, 那么應(yīng)該使用一個(gè)包裝類來對該方法進(jìn)行包裝. 這個(gè)包裝類還是要實(shí)現(xiàn)一個(gè)接口. 用它的時(shí)候注入該接口即可. 這樣測試的時(shí)候就可以為包裝類創(chuàng)建測試替身了, 并把全局狀態(tài)解耦.
- 使用可依賴注入(IoC/DI)的單例體, 這種單例體是由IoC容器創(chuàng)建的.
?
例子
就舉一個(gè)例子吧.
有這樣一個(gè)獲取當(dāng)前登錄用戶權(quán)限的類, 它使用的是單例模式:
這個(gè)是典型的單例模式, 它會(huì)保證在程序中只返回一個(gè)實(shí)例, 這里就不多介紹了.
?
下面這個(gè)Service會(huì)調(diào)用上面這個(gè)Auth類:
Auth是單例模式的, 而且還調(diào)用了靜態(tài)方法.
現(xiàn)在的狀態(tài)是, OfficeService和Auth所包含的全局狀態(tài)緊密的耦合到了一起.?
?
如何解決問題
首先應(yīng)該把單例模式去掉, Auth類只保留兩個(gè)屬性和一個(gè)方法:
?
然后在service里面應(yīng)該注入IAuth接口并使用:
?
那么接下來就需要保證這個(gè)IAuth無論在程序中注入了多少次, 都是同一個(gè)實(shí)例.
這時(shí)就需要使用依賴注入(DI) 庫了. 現(xiàn)在的DI庫通常允許指定IoC容器中每對綁定服務(wù)的作用范圍(Scope), 或叫做生命周期管理.
例如ASP.NET Core內(nèi)置的IoC容器就內(nèi)置了這種功能. 在ASP.NET Core 項(xiàng)目的Startup類里, 這樣寫就可以保證每次請求IAuth的時(shí)候只會(huì)得到同一個(gè)對象實(shí)例:
現(xiàn)在這個(gè)"單例"的工作是由IoC容器來負(fù)責(zé)了. 在其它地方正常的注入IAuth使用即可.
?
先寫到這, 本文的概念性內(nèi)容和更多的例子請參考Angular創(chuàng)始的人這篇文章:?http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的.NET Core TDD 前传: 编写易于测试的代码 -- 全局状态的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 剪切文件_lammps模拟带缺陷镍板剪切
- 下一篇: python 隐藏命令行窗口_pytho