🎯 什麼情境該想到我
當你「要測的程式碼直接呼叫了某個靜態方法,而那個靜態方法裡面藏著測試時碰不得的東西」的時候。
書中 p.291 描述這個處境:「如果你的專案中有靜態方法,則很可能它們並不會給你帶來麻煩,除非其中包含了一些你沒法或不想在測試的時候依賴的東西。(這個的技術術語叫做『靜態黏著』。)遇到這類情況,你可能會想『唉,要是有物件接縫就好了,那樣的話就可以利用它來嵌入測試用的行為了。』」
解法(p.291):「方案之一便是往目標類上引入委託實例方法。這時候,你需要想辦法將對那些靜態方法的呼叫替換為對目標類的實例方法的呼叫。」書中的示範是在一個只含靜態方法的類上加一個實例方法,讓它「只是簡單地將呼叫轉發至靜態方法」,然後把呼叫端從直接呼叫靜態方法,改成呼叫傳進來的那個物件的實例方法。
書中 p.290 也交代了這類靜態方法從哪來:「人們會因為各種各樣的原因使用靜態方法。其中最常見的原因便是為了實現單件模式(書 p.293)。而另一個常見的原因是使用靜態方法來建立實用類。」實用類「一般沒有任何實例變數和實例方法。而是全部由靜態方法和靜態常量組成」,多數時候是因為「無法為一組方法尋找出合適的公共抽象」。
⚙️ 怎麼用(步驟)
書中 p.292「步驟」小節:
- 找出會在測試中帶來問題的那個靜態方法。
- 在它所屬類上新建一個實例方法(記得用簽名保持手法)。讓該實例方法委託那個靜態方法。
- 找出你想要納入測試的類中有哪些地方使用了那個靜態方法。使用參數化方法(書 p.301)或其他解依賴技術來提供一個實例給程式碼中想要呼叫那個靜態方法的地方(即改為從該實例間接呼叫那個靜態方法)。
🧪 我實際套用的紀錄
- (待填)
⚠️ 注意 / 什麼時候不適用
- 會讓設計暫時變醜,而且作者自己承認。 p.292:「對於許多靜態方法來說,該技術還算是相當直觀的。然而當遇到實用類的時候,你可能就會感覺不自然了。一個具有 5 到 10 個靜態方法,卻只有一兩個實例方法的類看起來的確挺怪異的。而如果這僅有的兩個實例方法還只是一層空殼,其功能只是將任務簡單地轉發給相應的靜態方法,那就更加怪異了。」
- 但這是有出口的醜。 同段接著說:「但好處是,該技術引入了物件接縫,後者使你能夠在測試時替換進另一種行為。隨著時間的推移,你可能就會發現每個對該類的方法的呼叫都通過實例方法進行轉發了,這時候你便可以將那些靜態方法的方法體轉移到相應的實例方法中,並刪除所有靜態方法了。」——所以委託空殼是過渡態,不是終點。
- 需要額外的重構才能真的塞得進去。 p.292:「注意,只有當我們有辦法在外部建立一個⋯⋯物件從而能通過該物件來呼叫⋯⋯時,該做法才能成功。這就意味著我們需要做一些額外的重構,不過,在靜態型別的語言裡,可以依靠編譯器(書 p.251)來幫我們將物件放置到位。」
- 書中對實用類本身也有保留(p.290):Java 在寫作當時尚不支援在基本型別上呼叫數學方法,因此實用類是個正當的解決方案,「但它同時也是個特例。在幾乎所有的情況下,你也可以使用傳統的帶有實例資料和方法的類來完成工作」。
🔗 相關工具
- 工具-打破依賴以便測試 — 這條技術屬於的大類
- 引入靜態設置方法 — 同樣處理靜態/全域造成的相依;p.290 指出靜態方法最常見的來源就是單件
- 參數化方法 — 步驟第 3 步指名用它(書 p.301)把實例送到呼叫處
- 介面提取 — 有了實例方法之後,常接著抽介面才能換上偽物件
- 回連 修改程式碼的藝術