角色
該設(shè)計(jì)引入了由系統(tǒng)中的對(duì)象扮演的下列角色:
· 目標(biāo)對(duì)象:正在測(cè)試的對(duì)象
· 合作者對(duì)象:由目標(biāo)對(duì)象創(chuàng)建或獲取的對(duì)象
· 模仿對(duì)象:遵循模仿對(duì)象模式的合作者的子類(或?qū)崿F(xiàn))
· 特殊化對(duì)象:覆蓋創(chuàng)建方法以返回模仿對(duì)象而不是合作者的目標(biāo)的子類
技巧
重構(gòu)由許多小的技術(shù)性步驟組成。這些步驟統(tǒng)稱為技巧。如果您象按照食譜那樣嚴(yán)格遵循這些技術(shù),那么您在學(xué)習(xí)重構(gòu)時(shí)應(yīng)該沒(méi)有太大的麻煩。
標(biāo)識(shí)創(chuàng)建或獲取合作者的代碼的所有出現(xiàn)。
將抽取方法重構(gòu)應(yīng)用于這個(gè)創(chuàng)建代碼,創(chuàng)建工廠方法(在Fowler書籍的第110頁(yè)中討論;有關(guān)更多信息,請(qǐng)參閱參考資料一節(jié))。
確保目標(biāo)對(duì)象及其子類可以訪問(wèn)工廠方法。(在 Java 語(yǔ)言中,使用 protected 關(guān)鍵字)。
在測(cè)試代碼中,創(chuàng)建模仿對(duì)象且實(shí)現(xiàn)與合作者相同的接口。
在測(cè)試代碼中,創(chuàng)建擴(kuò)展(專用于)目標(biāo)對(duì)象的特殊化對(duì)象。
在特殊化對(duì)象中,覆蓋創(chuàng)建方法以返回為測(cè)試提供的模仿對(duì)象。
可選的:創(chuàng)建單元測(cè)試以確保原始目標(biāo)對(duì)象的工廠方法仍返回正確的非模仿對(duì)象。
示例:ATM
設(shè)想您正在編寫用于銀行自動(dòng)柜員機(jī)(Automatic Teller Machine)的測(cè)試。其中一個(gè)測(cè)試可能類似于清單 2:
清單 2. 初始單元測(cè)試,在模仿對(duì)象引入之前:
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)品代碼,在重構(gòu)之前:
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();
}
該方法將起作用,遺憾的是,它有一個(gè)副作用:支票帳戶余額比測(cè)試開始時(shí)少,這使得其它測(cè)試變得更困難。有一些解決這種困難的方法,但它們都會(huì)增加測(cè)試的復(fù)雜性。更糟的是,該方法還需要對(duì)管理貨幣的系統(tǒng)進(jìn)行三次往返。
要修正這個(gè)問(wèn)題,第一步是重構(gòu) AtmGui 以允許我們用模仿事務(wù)替換實(shí)際事務(wù),如清單 4 中所示(比較粗體的源代碼以查看我們正在更改什么):
清單 4. 重構(gòu)
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();
}