🎯 什麼情境該想到我
當你「用的是直譯語言,只想讓某一個方法在測試中不要真的被執行」的時候。
書上的定位:「一些新的直譯語言提供了非常好的解依賴途徑。在被直譯的時候,方法可以被及時重定義。」(p.323)
書中的 Ruby 例子:Account 有個 deposit 會呼叫 report_deposit。「如果我們不想看到 report_deposit 在測試中被執行,則只需在測試檔案中重定義,並將測試程式碼放在新的定義之後。」(p.323)
關鍵在於「只重定義方法、不重定義類別」: 「有一點非常值得注意,即我們並沒有重定義整個的 Account 類別,而只是重定義了它的 report_deposit 方法。Ruby 直譯器將 ruby 程式碼檔案的每行都看作可執行語句。而 class Account 語句就相當於打開了 Account 類別的定義,從而新的方法定義可以被添加進去。而 def report_deposit(value) 語句則開始往打開的類別中添加新方法。Ruby 直譯器並不關心該方法是否已有定義體存在,如果有的話就替換掉。」(p.323–324)
(簡體版譯者在此加註:這個手法一般叫「文本替換」。)
⚙️ 怎麼用(步驟)
書中列出的步驟——標題就寫明「在 Ruby 中使用文本重定義的步驟」(p.324):
- 找出你想要替換定義的方法所在的類別。
- 在測試原始檔開頭添加一行 require,引入包含目標類別的模組。
- 在測試原始檔的開頭給每一個你想要替換的方法提供新的定義。
順序很重要:書中示範的檔案結構是 require → 重新打開類別、重定義方法 → 然後才是測試程式碼,並明講要「將測試程式碼放在新的定義之後」(p.323)。
🧪 我實際套用的紀錄
- (待填)
⚠️ 注意 / 什麼時候不適用
-
⚠️ 語言限制:書中示範與步驟都限定在直譯語言,具體例子是 Ruby。(p.323–324)它靠的是「程式碼檔案每一行都是可執行語句、類別定義可以被重新打開」這個語言性質。
-
⚠️ 主要缺點:重定義是全域且不可逆的。 書中的側欄(p.324):
「Ruby 中的文本重定義也有它的缺點:新方法對舊方法的替換會一直有效,直到程式結束。所以如果你後面忘記了某方法已被重定義了的話就可能會遇到麻煩。」
換句話說,你在測試檔開頭做的這件事會污染整個測試程序的其餘部分——這是拿「完全不用改產品程式碼」換來的代價。
-
C/C++ 也有對應做法,但走的是另一條路。 「其實我們在 C/C++ 中也可以進行文本重定義——使用預處理。關於這個的一個例子可以參考第 4 章描述預處理期接縫(29 頁)一節中的例子。」(p.324)
🔗 相關工具
- 工具-打破依賴以便測試 — 這條技術屬於的大類
- 模板重定義 — 同樣是不靠繼承、直接換掉定義的路線,但走的是 C++ 泛型
- 子類化並重寫方法 — 物件導向語言中的預設解法;本手法免去了建立測試子類別這一步
- 回連 修改程式碼的藝術