閑人野居
          好好學習,天天向上
          posts - 57,  comments - 137,  trackbacks - 0

          關于單元測試,模擬對象一直是不可缺少的,尤其對于復雜的應用來說。
          ?????? 這么多的模擬對象框架中,個人覺得比較好用的當屬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)
          ??? ...

          增加行為

          ?????? 讓我們開始第二個測試。如果documentclassUnderTest增加,我們期待調用
          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 布衣郎 閱讀(3078) 評論(1)  編輯  收藏 所屬分類: 單元測試

          FeedBack:
          # re: EasyMock 2 使用指南
          2006-09-21 16:14 | 123bingbing
          如果你是編程高手,這里將是你一個展現自我的新舞臺----www.mylinux.com.cn  回復  更多評論
            

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           

          <2006年9月>
          272829303112
          3456789
          10111213141516
          17181920212223
          24252627282930
          1234567

          常用鏈接

          留言簿(12)

          隨筆分類(59)

          隨筆檔案(57)

          blog

          java

          uml

          搜索

          •  

          積分與排名

          • 積分 - 357324
          • 排名 - 155

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 蓬莱市| 贵定县| 白山市| 沭阳县| 元朗区| 股票| 宜章县| 若尔盖县| 延安市| 石棉县| 利川市| 开原市| 莱西市| 清水河县| 垣曲县| 祁连县| 铅山县| 东乌珠穆沁旗| 平遥县| 宿州市| 微博| 定陶县| 南木林县| 吉安县| 石阡县| 彩票| 宁武县| 固始县| 自贡市| 太保市| 出国| 石首市| 许昌县| 从化市| 剑阁县| 昭平县| 武夷山市| 利津县| 垣曲县| 祁连县| 巫山县|