EasyMock2的使用指南(轉載)

轉載自: http://blog.csdn.net/zyl623/archive/2006/09/20/1255246.aspx

 

關於單元測試,模擬對象一直是不可缺少的,尤其對於複雜的應用來說。
       這麼多的模擬對象框架中,個人覺得比較好用的當屬EasyMock了。當然JMock也不錯。
       下面簡單介紹一下EasyMock 。
     
       EasyMock 2
主要用於給指定的接口提供模擬對象。 <?XML:NAMESPACE PREFIX = O />

模擬對象只是模擬領域代碼直接的部分行爲,能檢測是否他們如定義中的被使用。使用 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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章