關于如何提高代碼可測試性的一些看法
在這些條件下,偽造一個RGHConnection對象最好的方法是對RGHConnection類應用接口提取。如果你手頭一個支持重構的工具,那么它很可能也會支持接口提取方法。我們來看一下接口提取后的情況:
<interface> IRGHConnection +connect() + disconnect() +RFDIReportFor(id:int):RFDIReport +ACTIOReportFor(customerID:int) ACTIOReport |
由于retry()和fromPacket()不屬于業務相關方法因此只需要在實現類中增加這兩個方法,至此我們可以輕松的構建出一個FackeConnection類,并使它能夠提供我們所需要的反饋信息,然后將這個偽造的對象用在測試中:
public class FakeConnection implements IRGHConnection { public RFDIReport report; public void connect() {} public void disconnnect(){} public RFDIReport RFDReportFor(int id) {return report;} public ACTIOReport ACTIOReportFor(int customerID) {return null;} } |
下面我們來寫測試
void testNoSuccess()throws Exception{ CreditMaster master = new CreditMaster("crm2.mas",true); IRGHConnection connection = new FakeConnection(); CreditValidator validator = new CreditValidator(connection,master,"a"); connection.report = new RFDReport(....); Certificate result = validator.validatorCustomer(new Customer(...)); assertEquals(Certificate.VALID,result.getStatus()); } |
雖然FakeConnection類看起來有點奇怪:它的方法要么是空的要么就簡單的返回null。這種情形并不常見。更糟的是,它有一個任何人都可以看到的并隨意設置的公共變量。這樣一個類似似乎違反了所有的良好準則。但你要看到,實際上并非如此。對于一個用來使得測試可行的類,規則是所不同的。FakeConnection中的代碼并非產品代碼。它永遠也不屬于最終投入運行的應用,而只是為了測試工具和測試而誕生。
有了這個fake類我們接下去便可以做更多的相關測試。這就是提高代碼可測試性和遇到教難構造的的類的時候所采取的一種方法。若代碼設計階段就將RGHConnection設計為接口,那么在后面的測試中會是測試更加方便,使代碼在后期的重構也會更加方便。
本文是在讀了《Working Effectively with legacy Code 》第九章,關于在無法將類放入測試用具中時遇到的四種最為常見的問題:
(1)無法輕易創建該類的對象。
(2)當該類位于測試用具中時,測試用具無法輕易通過編譯構建。
(3)我們需要用到的構造函數具有副作用。
(4)構造函數中有一些要緊的工作,我們需要感知到它們。
這四個問題在進行單元測試或者接口測試的時候,會對測試工作造成很大的阻礙,這就是一個代碼可測性的問題。當遇到這樣的問題的時候,有兩種方法,第一、強行構建一個類去完成測試,但是這會造成測試的時候大部分工作都耗費在構建這樣一個類的過程中;第二、重構代碼,使代碼具有可測性。本文將通過書中的列子來簡單介紹一下如何提高代碼的可測性。
如在一個計費系統中,我們有一個未測試的Java類:CreditValidator。
public class CreditValidator { public CreditValidator (RGHConnection connection, CreditMaster master, String validatorID) { } Certificate validateCustomer(Customer customer) throws InvalidaCredit{ } public class RGHConnection { public RGHConnection(int port, String Name, String passwd) throws IOException { } } } |
我們可以看到CreditValidator構造函數含有三個參數RGHConnection,CreditMaster,validatorID。其中RGHConnection對象在構造時會連接到一個服務器,這個鏈接被用來從服務器上獲取必要的信息,以檢查客戶的余額。
寧一個類CreditMaster,則提供一些我們在檢查余額的過程中會用到的策略信息。該類的構造函數會從一個文件中加載相關信息,并把這些信息保存在內存中以備后用。
如果按照我們開頭講的強制構造一個類來完成測試,如下所示:
public void testCreate() throws Exception { RGHConnection connection = new RGHConnection(DEFAULT_PORT,"admin","rii8ii9s"); CreditMaster master = new CreditMaster ("crm2.mas",true); CreditValidator validator = new CreditValidator(connection,master,"a"); } |
雖然我們構建一個這個樣的類,但是你能忍受這個測試的速度,根據《Working Effectively with legacy Code 》書中提到大于1秒的單元測試,都不叫單元測試。因此在測試中建立到服務器的連接并不是一個好的主意。首先其好事就比較長,況且服務器也并不總是處于服務狀態。可想而知RGHConnection是一個令人惱火的參數。我們的設想是:若能創建某種偽造的RGHConnection對象并使CreditValidator相信它是一個真正的RGHConnection的話,就可以避開所有鏈接的問題了。
首先來看一下RGHConnection所擁有的方法:
RGHConnection + RGHConnection(port,name,password) + connect() + disconnect() +RFDIReportFor(id:int):RFDIReport +ACTIOReportFor(customerID:int) ACTIOReport +retry() +fromPacket():RFPacket |
看上去RGHConnection中有一些方法是用來處理與連接相關的任務的:如connect、disconnect以及retry。另外還有一些業務方法。因此如果要偽造一個RGHConnection對象的話,那么這個偽造的對象也必須擁有這些方法也能提供一樣的信息。
posted on 2012-06-12 09:26 順其自然EVO 閱讀(482) 評論(0) 編輯 收藏 所屬分類: 測試學習專欄