EasyMock 2 使用指南

关于单元测试,模拟对象一直是不可缺少的,尤其对于复杂的应用来说。
       这么多的模拟对象框架中,个人觉得比较好用的当属EasyMock了。当然JMock也不错。
       下面简单介绍一下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
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值