模擬對象

物件導向程序設計中,模擬對象(英語:mock object,也譯作模仿對象)是以可控的方式模擬真實對象行為的假的對象。程式設計師通常創造模擬對象來測試其他對象的行為,很類似汽車設計者使用碰撞測試假人模擬車輛碰撞中人的動態行為。

為什麼要使用模擬對象

單元測試中,模擬對象可以模擬複雜的、真實的(非模擬)對象的行為, 如果真實的對象無法放入單元測試中,使用模擬對象就很有幫助。

在下面的情形,可能需要使用模擬對象來代替真實對象[1]

  • 真實對象的行為是不確定的(例如,當前的時間或當前的溫度);
  • 真實對象很難搭建起來;
  • 真實對象的行為很難觸發(例如,網絡錯誤);
  • 真實對象速度很慢(例如,一個完整的資料庫,在測試之前可能需要初始化);
  • 真實的對象是用戶界面,或包括用戶界面在內;
  • 真實的對象使用了回調機制;
  • 真實對象可能還不存在;
  • 真實對象可能包含不能用作測試(而不是為實際工作)的信息和方法。

例如,一個可能會在特定的時間響鈴的鬧鐘程序可能需要外部世界的當前時間。要測試這一點,測試一直要等到鬧鈴時間才知道鬧鐘程序是否正確地響鈴。如果使用一個模擬對象替代真實的對象,可以變成提供一個鬧鈴時間(不管是否實際時間),這樣就可以隔離地測試鬧鐘程序。

技術細節

模擬對象具有和要模擬的真實對象的相同的接口,可以讓調用該接口的對象不知道在使用真實對象還是模擬對象。

現有的許多模擬對象框架允許程式設計師指定模擬對象上的哪些方法,將按照什麼順序被調用,以及傳入什麼參數,將返回什麼值。這樣,複雜對象(例如網絡套接字)的行為將可以使用模擬對象來模擬,允許程式設計師來發現被測對象在可能各種存在的狀態是否響應正確。

模擬對象,虛擬對象和樁

一些作者[2] 明確區分虛擬對象(fake)和模擬對象。虛擬對象比較簡單,簡單實現所代表的對象相同的接口,並返回預先安排好的應答。這樣一來虛擬對象僅僅提供了一組方法樁

在「單元測試的藝術」[3]一書中,模擬對象被描述為幫助決定測試通過與否的虛擬對象,通過驗證對象上是否發生了交互。其他的都被定義為樁。在該書中,「虛擬對象」(fake)是指所有非真實的對象。基於使用,或者是樁,或者是模擬對象。

從這個角度講,模擬對象多做了一些工作:它們方法實現中包括斷言。這就是說,這個意義上的真正的模擬對象將會檢查每個調用的上下文— 可能會檢查器上方法的調用順序,可能對方法調用的參數數據進行檢驗。

設定預期結果

考慮一個授權子系統被模擬的例子。模擬對象與真正的授權類相同,實現了isUserAllowed(task : Task) : boolean[4] 方法。如果暴露一個真實對象中沒有的屬性 isAllowed : boolean就會帶來許多便利,測試代碼可以很容易地設置預期的結果,用戶通過授權,或沒有通過,這樣,兩種情況下可以很容易地測試系統的行為。

同樣,只有模擬對象才有的設置可以確保對子系統的後續調用將會導致異常拋出,或沒有反應的掛起,或返回null等。這樣,開發客戶端的行為時,可以對後端子系統中的所有實際的故障的條件以及預期的響應進行測試。沒有這樣簡單而靈活的模擬系統,對於每一種情形進行測試將是十分費力的。

記錄日誌字符串

一個模擬的資料庫對象的保存方法save(person : Person)可能包含許多(如果有)實現代碼。可能檢查存在與否,可能驗證要保存的Person對象(參見上述的虛擬對象與模擬對象的討論),但是除此以外可能沒有其他的實現代碼。

這就錯過了機會,模擬方法可以記錄一條日誌到公共的的日誌字符串。日誌可以簡單地寫「Person saved」[5],也可以寫person對象的詳細信息,如名字或ID。這樣,如果測試代碼在對模擬資料庫進行了一系列操作後檢查日誌字符串的最終內容,就能夠驗證資料庫保存方法的執行次數是否與預期相符。 這種方法可以發現不可見的性能問題,例如一個開發人員對丟失數據感到緊張,編寫了多次對 save()的調用,而一次調用就已經足夠了。

在測試驅動開發中的使用

使用測試驅動開發 (TDD)方法的程式設計師在編寫軟體時會使用模擬對象。模擬對象滿足更複雜的真實對象的接口需求,並代替真實對象的位置,有了模擬對象,程式設計師就可以對一個領域的功能性進行單元測試,而不需要實際調用複雜的下層或協作的[6] 使用模擬對象使得開發人員可以關注與被測系統(SUT)的行為的測試,而不需要擔心被測系統的依賴關係。例如,測試在特定狀態下一個基於多個對象的複雜算法,如果使用模擬對象代替真實對象可以很容易地表達出來。

除了複雜性問題和關注點分離帶來的好處,還有實際的速度問題。使用測試驅動開發 (TDD)開發一段實際的軟體很容易就有數百個單元測試。如果這些單元測試中許多都涉及到與資料庫,Web服務和其他進程間通訊網絡系統的通訊,單元測試的組合會很快會慢到無法執行例行測試。而這會導致壞的習慣以及程式設計師不願意維護測試驅動開發的基本原則。

當模擬對象被替換為真實對象,端到端的功能仍需要進一步的測試。這將不再是單元測試,而是集成測試

局限性

模擬對象的使用可能會將單元測試與被測代碼的實現耦合得很緊。例如,許多模擬對象框架允許開發人員指定模擬對象上方法被調用的次序和調用的次數,這樣,測試通過後對代碼進行重構,即使方法依然遵守以前實現的契約,也可能會造成測試失敗。這說明單元測試應當測試方法的外部行為,而非其內部實現。在單元測試測試用例中過度使用模擬對象可能導致隨著系統的發展,不斷進行的重構會造成維護測試本身的工作量出現顯著的增長。在發展過程中,這種測試的不正確地維護可能會漏報錯誤,而在使用真實對象進行的測試中會捕捉到。相反,與設置好整個真實對象相比,簡單地模擬一個方法可能需要更少的配置,因此減少了需要的維護工作。

模擬對象必須要準確地建模它們要模擬的對象的行為,然而,如果要模擬的對象來自另一個開發人員或項目,或者如果還沒有開發出來,準確的建模是很難做到。如果沒有正確建模行為,那麼可能會單元測試記錄通過,而真正運行時,在同樣條件下可能會造成測試失敗。[7]

參見

參考文獻

  1. ^ Tim Mackinnon, Steve Freeman, Philip Craig Endo-Testing: Unit Testing with Mock Objects, eXtreme Programming and Flexible Processes in Software Engineering - XP2000
  2. ^ Feathers, Michael. Sensing and separation. Working effectively with legacy code. NJ: Prentice Hall. 2005: 23 et seq. ISBN 0-13-117705-2. 
  3. ^ Osherove, Roy. Interaction testing with mock objects et seq. The art of unit testing. Manning. 2009. ISBN 978-1933988276. 
  4. ^ 這些示例使用了類似統一建模語言中使用的命名法
  5. ^ Beck, Kent. Test-Driven Development By Example. Boston: Addison Wesley. 2003: 146–7. ISBN 0-321-14653-0. 
  6. ^ Beck, Kent. Test-Driven Development By Example. Boston: Addison Wesley. 2003: 144–5. ISBN 0-321-14653-0. 
  7. ^ InJava.com頁面存檔備份,存於網際網路檔案館) to Mocking | O'Reilly Media

外部連結