本站首页    管理页面    写新日志    退出


«September 2025»
123456
78910111213
14151617181920
21222324252627
282930


公告
 本博客在此声明所有文章均为转摘,只做资料收集使用。

我的分类(专题)

日志更新

最新评论

留言板

链接

Blog信息
blog名称:
日志总数:1304
评论数量:2242
留言数量:5
访问次数:7612062
建立时间:2006年5月29日




[集成测试]EasyMock 2 使用指南
软件技术

lhwork 发表于 2006/9/21 10:19:35

关于单元测试,模拟对象一直是不可缺少的,尤其对于复杂的应用来说。       这么多的模拟对象框架中,个人觉得比较好用的当属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


阅读全文(2439) | 回复(0) | 编辑 | 精华
 



发表评论:
昵称:
密码:
主页:
标题:
验证码:  (不区分大小写,请仔细填写,输错需重写评论内容!)



站点首页 | 联系我们 | 博客注册 | 博客登陆

Sponsored By W3CHINA
W3CHINA Blog 0.8 Processed in 0.063 second(s), page refreshed 144764706 times.
《全国人大常委会关于维护互联网安全的决定》  《计算机信息网络国际联网安全保护管理办法》
苏ICP备05006046号