角色
該設計引入了由系統(tǒng)中的對象扮演的下列角色:
· 目標對象:正在測試的對象
· 合作者對象:由目標對象創(chuàng)建或獲取的對象
· 模仿對象:遵循模仿對象模式的合作者的子類(或實現(xiàn))
· 特殊化對象:覆蓋創(chuàng)建方法以返回模仿對象而不是合作者的目標的子類
技巧
重構由許多小的技術性步驟組成。這些步驟統(tǒng)稱為技巧。如果您象按照食譜那樣嚴格遵循這些技術,那么您在學習重構時應該沒有太大的麻煩。
標識創(chuàng)建或獲取合作者的代碼的所有出現(xiàn)。
將抽取方法重構應用于這個創(chuàng)建代碼,創(chuàng)建工廠方法(在Fowler書籍的第110頁中討論;有關更多信息,請參閱參考資料一節(jié))。
確保目標對象及其子類可以訪問工廠方法。(在 Java 語言中,使用 protected 關鍵字)。
在測試代碼中,創(chuàng)建模仿對象且實現(xiàn)與合作者相同的接口。
在測試代碼中,創(chuàng)建擴展(專用于)目標對象的特殊化對象。
在特殊化對象中,覆蓋創(chuàng)建方法以返回為測試提供的模仿對象。
可選的:創(chuàng)建單元測試以確保原始目標對象的工廠方法仍返回正確的非模仿對象。
示例:ATM
設想您正在編寫用于銀行自動柜員機(Automatic Teller Machine)的測試。其中一個測試可能類似于清單 2:
清單 2. 初始單元測試,在模仿對象引入之前:
public void testCheckingWithdrawal() {
float startingBalance = balanceForTestCheckingAclearcase/" target="_blank" >ccount();
AtmGui atm = new AtmGui();
insertCardAndInputPin(atm);
atm.pressButton("Withdraw");
atm.pressButton("Checking");
atm.pressButtons("1", "0", "0", "0", "0");
assertContains("$100.00", atm.getDisplayContents());
atm.pressButton("Continue");
assertEquals(startingBalance - 100,
balanceForTestCheckingAccount());
}
另外,AtmGui 類內(nèi)部的匹配代碼可能類似于清單 3:
清單 3. 產(chǎn)品代碼,在重構之前:
private Status doWithdrawal(Account account, float amount) {
Transaction transaction = new Transaction();
transaction.setSourceAccount(account);
transaction.setDestAccount(myCashAccount());
transaction.setAmount(amount);
transaction.process();
if (transaction.successful()) {
dispense(amount);
}
return transaction.getStatus();
}
該方法將起作用,遺憾的是,它有一個副作用:支票帳戶余額比測試開始時少,這使得其它測試變得更困難。有一些解決這種困難的方法,但它們都會增加測試的復雜性。更糟的是,該方法還需要對管理貨幣的系統(tǒng)進行三次往返。
要修正這個問題,第一步是重構 AtmGui 以允許我們用模仿事務替換實際事務,如清單 4 中所示(比較粗體的源代碼以查看我們正在更改什么):
清單 4. 重構
AtmGui private Status doWithdrawal(Account account, float amount) {
Transaction transaction = createTransaction();
transaction.setSourceAccount(account);
transaction.setDestAccount(myCashAccount());
transaction.setAmount(amount);
transaction.process();
if (transaction.successful()) {
dispense(amount);
}
return transaction.getStatus();
}
protected Transaction createTransaction() {
return new Transaction();
}