關于單元測試,模擬對象一直是不可缺少的,尤其對于復雜的應用來說。
?????? 這么多的模擬對象框架中,個人覺得比較好用的當屬EasyMock了。當然JMock也不錯。
?????? 下面簡單介紹一下EasyMock?。(基本翻譯EasyMock的文檔,可能有些地方不是很恰當)
?????
???????EasyMock 2
主要用于給指定的接口提供模擬對象。
模擬對象只是模擬領域代碼直接的部分行為,能檢測是否他們如定義中的被使用。使用
Mock
對象,來模擬合作接口,有助于隔離測試相應的領域類。
創建和維持
Mock
對象經常是繁瑣的任務,并且可能會引入錯誤。
EasyMock 2
動態產生
Mock
對象,不需要創建,并且不會產生代碼。
有利的方面:
不需要手工寫類來處理
mock
對象。
支持安全的重構
Mock
對象:測試代碼不會在運行期打斷當重新命名方法或者更改方法參數。
支持返回值和例外。
支持檢察方法調用次序,對于一個或者多個
Mock
對象。
不利的方面:
2.0
僅使用于
java 2
版本
5.0
或者以上
????
??? 以一個例子來說明如何使用EasyMock:
???假設有一個合作接口Collaborator:
??????????? package org.easymock.samples; ? public interface Collaborator { ??? void documentAdded(String title); ??? void documentChanged(String title); ??? void documentRemoved(String title); ??? byte voteForRemoval(String title); ??? byte[] voteForRemovals(String[] title); }
|
我們主要的測試類為:
package org.easymock.samples; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; public class ClassUnderTest { ??? private Set<Collaborator> listeners = new HashSet<Collaborator>(); ??? private Map<String, byte[]> documents = new HashMap<String, byte[]>(); ??? public void addListener(Collaborator listener) { ??????? listeners.add(listener); ??? } ??? public void addDocument(String title, byte[] document) { ??????? boolean documentChange = documents.containsKey(title); ??????? documents.put(title, document); ??????? if (documentChange) { ??????????? notifyListenersDocumentChanged(title); ??????? } else { ??????????? notifyListenersDocumentAdded(title); ??????? } ??? } ??? public boolean removeDocument(String title) { ??????? if (!documents.containsKey(title)) { ??????????? return true; ??????? } ??????? if (!listenersAllowRemoval(title)) { ??????????? return false; ??????? } ??????? documents.remove(title); ??????? notifyListenersDocumentRemoved(title); ??????? return true; ??? } ??? public boolean removeDocuments(String[] titles) { ??????? if (!listenersAllowRemovals(titles)) { ??????????? return false; ??????? } ??????? for (String title : titles) { ??????????? documents.remove(title); ??????????? notifyListenersDocumentRemoved(title); ??????? } ??????? return true; ??? } ??? private void notifyListenersDocumentAdded(String title) { ??????? for (Collaborator listener : listeners) { ??????????? listener.documentAdded(title); ??????? } ??? } ??? private void notifyListenersDocumentChanged(String title) { ??????? for (Collaborator listener : listeners) { ??????????? listener.documentChanged(title); ??????? } ??? } ??? private void notifyListenersDocumentRemoved(String title) { ??????? for (Collaborator listener : listeners) { ??????????? listener.documentRemoved(title); ??????? } ??? } ??? private boolean listenersAllowRemoval(String title) { ??????? int result = 0; ??????? for (Collaborator listener : listeners) { ??????????? result += listener.voteForRemoval(title); ??????? } ??????? return result > 0; ??? } ??? private boolean listenersAllowRemovals(String[] titles) { ??????? int result = 0; ??????? for (Collaborator listener : listeners) { ??????????? result += listener.voteForRemovals(titles); ??????? } ??????? return result > 0; ??? } }
|
第一個Mock 對象
我們將創建test case 并且圍繞此理解相關的EasyMock 包的功能。第一個測試方法,用于檢測是否刪除一個不存在的文檔,不會發通知給合作類。
??????????package org.easymock.samples; ? import junit.framework.TestCase; ? public class ExampleTest extends TestCase { ? ??? private ClassUnderTest classUnderTest; ??? private Collaborator mock; ? ??? protected void setUp() { ??????? classUnderTest = new ClassUnderTest(); ??????? classUnderTest.addListener(mock); ??? } ? ??? public void testRemoveNonExistingDocument() {??? ????????// This call should not lead to any notification ???? ???// of the Mock Object: ????????classUnderTest.removeDocument("Does not exist"); ??? } }
|
????對于多數測試類,使用EasyMock 2,我們只需要靜態引入org.easymock.EasyMock
的方法。
?????? ?import static org.easymock.EasyMock.*; import junit.framework.TestCase; ? public class ExampleTest extends TestCase { ? ??? private ClassUnderTest classUnderTest; ??? private Collaborator mock; ??? }
|
?????
為了取得Mock 對象,需要:
l???????? 創建Mock 對象從需要模擬的接口
l???????? 記錄期待的行為
l???????? 轉換到Mock對象,replay狀態。
例如:????? ?protected void setUp() { ??? ????mock = createMock(Collaborator.class); // 1 ??????? classUnderTest = new ClassUnderTest(); ??????? classUnderTest.addListener(mock); ??? }
?public void testRemoveNonExistingDocument() { ??????? // 2 (we do not expect anything) ??????? replay(mock); // 3 ??????? classUnderTest.removeDocument("Does not exist"); ??? }
|
??
在執行第三步后,mock 為Collaborator
接口的Mock對象,并且期待沒有什么調用。這就意味著,如果我們改變ClassUnderTest去調用此接口的任何方法,則Mock對象會拋出AssertionError:
???????? ?java.lang.AssertionError: ??Unexpected method call documentRemoved("Does not exist"): ??? at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29) ??? at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44) ??? at $Proxy0.documentRemoved(Unknown Source) ??? at org.easymock.samples.ClassUnderTest.notifyListenersDocumentRemoved(ClassUnderTest.java:74) ??? at org.easymock.samples.ClassUnderTest.removeDocument(ClassUnderTest.java:33) ??? at org.easymock.samples.ExampleTest.testRemoveNonExistingDocument(ExampleTest.java:24) ??? ...
|
增加行為
??????
讓我們開始第二個測試。如果document被classUnderTest增加,我們期待調用
mock.documentAdded()
在Mock對象使用document的標題作為參數:
??public void testAddDocument() { ?????? ?mock.documentAdded("New Document"); // 2 ??????? replay(mock); // 3 ??????? classUnderTest.addDocument("New Document", new byte[0]); ????}
|
如果classUnderTest.addDocument("New Document", new byte[0])
調用期待的方法,使用錯誤的參數,Mock對象會拋出AssertionError:
?java.lang.AssertionError: ??Unexpected method call documentAdded("Wrong title"): ??? documentAdded("New Document"): expected: 1, actual: 0 ??? at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29) ??? at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44) ??? at $Proxy0.documentAdded(Unknown Source) ??? at org.easymock.samples.ClassUnderTest.notifyListenersDocumentAdded(ClassUnderTest.java:61) ??? at org.easymock.samples.ClassUnderTest.addDocument(ClassUnderTest.java:28) ??? at org.easymock.samples.ExampleTest.testAddDocument(ExampleTest.java:30) ??? ...
|
同樣,如果調用多次此方法,則也會拋出例外:
?java.lang.AssertionError: ??Unexpected method call documentAdded("New Document"): ??? documentAdded("New Document"): expected: 1, actual: 1 (+1) ??? at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29) ??? at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44) ??? at $Proxy0.documentAdded(Unknown Source) ??? at org.easymock.samples.ClassUnderTest.notifyListenersDocumentAdded(ClassUnderTest.java:62) ??? at org.easymock.samples.ClassUnderTest.addDocument(ClassUnderTest.java:29) ??? at org.easymock.samples.ExampleTest.testAddDocument(ExampleTest.java:30) ??? ...
|
驗證行為
?????? 當我們指定行為后,我們將驗證實際發生的。當前的測試將會判斷是否Mock對象會真實調用。可以調用verify(mock)
來山正是否指定的行為被調用。
?public void testAddDocument() { ??????? mock.documentAdded("New Document"); // 2 ????????replay(mock); // 3 ??????? classUnderTest.addDocument("New Document", new byte[0]); ?????? ?verify(mock); ??? }
|
如果失敗,則拋出AssertionError
期待明顯數量的調用
到現在,我們的測試只是調用一個簡單的方法。下一個測試將會檢測是否已經存在document導致mock.documentChanged()
調用。為了確認,調用三次
?public void testAddAndChangeDocument() { ??????? mock.documentAdded("Document"); ??????? mock.documentChanged("Document"); ??????? mock.documentChanged("Document"); ??????? mock.documentChanged("Document"); ??????? replay(mock); ??????? classUnderTest.addDocument("Document", new byte[0]); ??????? classUnderTest.addDocument("Document", new byte[0]); ??????? classUnderTest.addDocument("Document", new byte[0]); ??????? classUnderTest.addDocument("Document", new byte[0]); ??????? verify(mock); ??? }
|
為了避免重復的mock.documentChanged("Document")
,EasyMock提供一個快捷方式。可以通過調用方法expectLastCall().times(int times)
來指定最后一次調用的次數。
??public void testAddAndChangeDocument() { ??????? mock.documentAdded("Document"); ??????? mock.documentChanged("Document"); ??????? expectLastCall().times(3); ??????? replay(mock); ??????? classUnderTest.addDocument("Document", new byte[0]); ??????? classUnderTest.addDocument("Document", new byte[0]); ??????? classUnderTest.addDocument("Document", new byte[0]); ??????? classUnderTest.addDocument("Document", new byte[0]); ??????? verify(mock); ??? }
|
指定返回值
?????? 對于指定返回值,我們通過封裝expect(T value)
返回的對象并且指定返回的值,使用方法andReturn(Object returnValue)于expect(T value)
.返回的對象。
例如:
?public void testVoteForRemoval() { ??????? mock.documentAdded("Document");?? // expect document addition ??????? // expect to be asked to vote for document removal, and vote for it ??????? expect(mock.voteForRemoval("Document")).andReturn((byte) 42); ??????? mock.documentRemoved("Document"); // expect document removal ??????? replay(mock); ??????? classUnderTest.addDocument("Document", new byte[0]); ??????? assertTrue(classUnderTest.removeDocument("Document")); ??????? verify(mock); ??? } ? ??? public void testVoteAgainstRemoval() { ??????? mock.documentAdded("Document");?? // expect document addition ??????? // expect to be asked to vote for document removal, and vote against it ??????? expect(mock.voteForRemoval("Document")).andReturn((byte) -42); ??????? replay(mock); ??????? classUnderTest.addDocument("Document", new byte[0]); ??????? assertFalse(classUnderTest.removeDocument("Document")); ? ??????verify(mock); ??? }
|
取代expect(T value)
調用,可以通過expectLastCall()
.來代替?expect(mock.voteForRemoval("Document")).andReturn((byte) 42);
|
等同于
?mock.voteForRemoval("Document"); expectLastCall().andReturn((byte) 42);
|
處理例外
對于指定的例外(更確切的:Throwables)被拋出,由expectLastCall()
和expect(T value)
返回的對象,提供了方法andThrow(Throwable throwable)
。方法不得不被調用記錄狀態,在調用Mock對象后,對于此指定了要拋出的Throwable。
基本的方法,已經說完了,當然這不能完全說明EasyMock的使用。更多的因素請參考EasyMock的文檔
http://www.easymock.org/Documentation.html
posted on 2006-09-20 20:38
布衣郎 閱讀(3077)
評論(1) 編輯 收藏 所屬分類:
單元測試