Java ♨️ 你可能不需要寫一堆 Interface — 談可測試性設計Testable Design
筆者曾任職 Yahoo,現在區塊鏈產業打滾,《經典駭客攻擊教程:給每個人的網站安全入門》線上課程講師 ,粉絲團《程式猿吃香蕉🍌》
可測試性設計 (Testable Design) 是指讓程式碼方便測試所做的設計,例如:
- 允許類別 (Class) 被繼承,便於在測試中替換掉原物件的行為
- 建立抽象的介面(Interface) 讓測試中替換掉物件更為容易
更進階一點,甚至會根據 GRASP 原則,設計純虛構 (pure fabrication) 的類別,來提高內聚性,降低耦合,讓 test cases 能專注在特定的邏輯驗證。
你可能會發現,在做可測試性設計時,也用了物件導向 (Object-oriented programming) 的觀念,兩者類似但卻又有些不同,舉個例子:
當我們對物件加上介面 (Interface)
- 以物件導向設計思考,是為了讓商業邏輯有擴充性。
- 以可測試性設計思考,是為了寫測試時便於替換原物件的行為,進行解依賴 (可以參考這一篇文章)。
雖然兩者的效果都是多型 (Polymorphism),但設計的出發點卻不一樣。甚至有時候可測試性設計還會破壞基於商業邏輯的物件導向設計。例如:以商業邏輯來說,某個物件並不需要擴充,但為了可測試性 (Testable) 所以你給該物件加了一個介面 (Interface)。
這讓我想起一件往事。
━━
在某前公司,我曾在一次 code review 中,看到同事把所有 Class 都加上了 Interface。然而,這樣做是有問題的:一來以物件導向的觀點看,抽象沒有做對;二來以可測性設計來說並不需要。怎麼說呢?
在現今的 Java 開發中,使用 Spring 幾乎已成標準配備,我們當時的專案也是。如果你使用 Spring 的 IoC 來管理物件的生命週期,便不需要為了可測試性 (Testable) 替物件寫一堆 Interfaces。
在《單元測試的藝術》書中提到:「如果出現了一個工具能夠解決可測試性問題,你就不需要為了可測試性來做特殊的設計。」 書中舉了動態語言的例子:「對於動態語言,程式碼天生就是可以測試的,因為你可以在執行期間替換掉任何東西,任何東西都能被抽換,用這種語言,你不需要考慮可測試性,可以按照自己的想法來設計。」
以 Spring 來說,Spring Test 以及 Mockito 等工具已經很方便地幫我們處理可測試性的問題,可以簡單用 @Mock 和 @Spy 以及 @InjectMocks 來替換掉物件行為。你不需要為了可測試性,而「特別」加上 Interface。設計要基於目的,完成目的要審慎檢視執行方法,才能省時又省力。
後來這段程式碼,在大家討論後並沒有實際的益處,最後沒有合併(merge)。
━━
可測試性設計讓我們在商業邏輯的物件設計之外,能有更多的思考。最後引用 Uncle Bob 對於可測試性設計的看法作為總結:
任何難以測試的設計都是糟糕的。純粹糟糕。為什麼?因為如果它很難測試,你就不會測試得夠好。而如果你測試不夠好,當你需要它運作時,它就不會運作。如果在你需要它運作時它沒有運作,那這個設計就是糟糕的。
Any design that is hard to test is crap. Pure crap. Why? Because if it’s hard to test, you aren’t going to test it well enough. And if you don’t test it well enough, it’s not going to work when you need it to work. And if it doesn’t work when you need it to work the design is crap.
若是喜歡我分享的內容,歡迎幫我按個拍手,可拍 50下,給我一點鼓勵,或是加入我的粉絲團《程式猿吃香蕉🍌》,一起分享軟體知識與心得!