少年阿宾

那些青春的岁月

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  500 Posts :: 0 Stories :: 135 Comments :: 0 Trackbacks

Introduction

EasyMock (http://www.easymock.org/)使用Javaproxy机制即时为接口提供Mock Object (和扩展类的object)。因EasyMock独特的期望记录方式,大部分重构不会印象Mock Object,故EasyMock很适合于TDD

EasyMocklicenseApache 2.0 (details see http://www.easymock.org/License.html)

 

Benchmark

         EasyMock部分模拟领域代码(domain code)的行为,可以检查代码是按照其定义被使用的。使用EasyMock,能够通过模拟协作者实现领域类的独立测试。潜台词是领域类的协作者可能还未实现,或者协作者需要实现访问数据库或文件系统,而携带这些代码的测试已经超出了单元测试的范畴。叫这种东西为“高仿”不过分吧。

 

Core Concepts

recording, expect:记录领域代码的期望行为(返回值、抛出异常)

replaymock的状态,此时mock正式参与到测试代码中;

verify:验证mock实际行为与预期行为是否一致。

 

A running example

EasyMock 3.1文档中案例 (http://www.easymock.org/EasyMock3_1_Documentation.html)

 

ICollaborator.java

package com.spike.easymock;

// 协作者接口,暂没有实现

public interface ICollaborator {

       // 记录主题

       void documentAdded(String title);

       // 改变主题

       void documentChanged(String title);

       // 删除主题

       void documentRemoved(String title);

       // 单个主题投票删除

       byte voteForRemoval(String title);

       // 多个主题投票删除

       byte voteForRemovals(String[] title);

       // 记录异常

       boolean logThrowable(Throwable e);

}

 

ClassUnderTest.java

package com.spike.easymock;

import java.util.*;

// 依赖于协作者接口的类,该例中协作者作为监听者

public class ClassUnderTest {

       // 监听者集合

       private final Set listeners = new HashSet();

       // 主题-投票数量记录映射

       private final Mapbyte[]> documents = new HashMapbyte[]>();

       // 增加监听者

       public void addListener(final ICollaborator listener) {

              listeners.add(listener);

       }

       // 增加单个主题的记录

       public void addDocument(final String title, final byte[] document) {

              final boolean documentChange = documents.containsKey(title);

              documents.put(title, document);

              // 就不同事件通知监听者

              if (documentChange) {

                     notifyListenersDocumentChanged(title);

              } else {

                     notifyListenersDocumentAdded(title);

              }

       }

       // 是否可以删除单个主题的记录

       public boolean removeDocument(final String title) {

              // 记录中不包含主题,允许删除

              if (!documents.containsKey(title)) {

                     return true;

              }

              // 记录中包含该主题,但监听者不允许删除该主题

              if (!listenersAllowRemoval(title)) {

                     return false;

              }

              // 记录中包含该主题,且监听者允许删除该主题,则执行删除操作

              documents.remove(title);

              notifyListenersDocumentRemoved(title);

              return true;

       }

       // 是否可以删除一些主题的记录

       public boolean removeDocuments(final String... titles) {

              if (!listenersAllowRemovals(titles)) {

                     return false;

              }

              for (final String title : titles) {

                     documents.remove(title);

                     notifyListenersDocumentRemoved(title);

              }

              return true;

       }

       // 通知监听者主题记录添加事件

       private void notifyListenersDocumentAdded(final String title) {

              for (final ICollaborator listener : listeners) {

                     listener.documentAdded(title);

              }

       }

       // 通知监听者主题记录改变事件

       private void notifyListenersDocumentChanged(final String title) {

              for (final ICollaborator listener : listeners) {

                     listener.documentChanged(title);

              }

       }

       // 通知监听者主题记录删除事件

       private void notifyListenersDocumentRemoved(final String title) {

              for (final ICollaborator listener : listeners) {

                     listener.documentRemoved(title);

              }

       }

       // 依据监听者就单个主题的投票统计,判断是否可以删除该主题

       private boolean listenersAllowRemoval(final String title) {

              int result = 0;

              for (final ICollaborator listener : listeners) {

                     result += listener.voteForRemoval(title);

              }

              return result > 0;

       }

       // 依据监听者就一些主题的投票统计,判断是否可以删除这些主题

       private boolean listenersAllowRemovals(final String... titles) {

              int result = 0;

              for (final ICollaborator listener : listeners) {

                     result += listener.voteForRemovals(titles);

              }

              return result > 0;

       }

}

 

DemoTest.java

package com.spike.easymock;

// 静态导入

import static org.easymock.EasyMock.*;

import static org.junit.Assert.*;

import java.util.List;

import org.easymock.*;

import org.junit.*;

//

public class DemoTest {

       // 使用了高仿的类

       private ClassUnderTest classUnderTest;

       // 高仿

       private ICollaborator mock;

        // @BeforeClass @AfterClass @After

     // ……

       @Before

       public void setUp() throws Exception {

              mock = createMock(ICollaborator.class); // 高仿制作

              classUnderTest = new ClassUnderTest();

              classUnderTest.addListener(mock);

       }

       // @Test

    // ……

 }

 

Basic EasyMock

@Test

public void testVoteForRemoval() {

            mock.documentAdded("Document");

              expect(mock.voteForRemoval("Document")).andReturn((byte) 42);

           mock.documentRemoved("Document");

              replay(mock);

              classUnderTest.addDocument("Document", new byte[0]);spike" name=image_operate_98111359816100182 alt="线形标注 2(带边框和强调线): JUnit断言" width=143 height=35 v:shapes="_x0000_s1028">             assertTrue(classUnderTest.removeDocument("Document"));              verify(mock);

       }

 

方法参数匹配(argument matcher)

       @Test

       public void testArgumentMatch() {

              String[] documents = new String[] { "Document 1", "Document 2" };

              String[] documents2 = new String[] { "Document 1", "Document 2" };

              expect(mock.voteForRemovals(aryEq(documents))).andReturn((byte) 42); // aryEq

              replay(mock);

              mock.voteForRemovals(documents2);

              verify(mock);

       }

 

预定义参数匹配[备注:记录与此以便查阅]

 

eq(X value)

Matches if the actual value is equals the expected value. Available for all primitive types and for objects.

anyBoolean(), anyByte(), anyChar(), anyDouble(), anyFloat(), anyInt(), anyLong(), 

anyObject(), anyObject(Class clazz),anyShort()

Matches any value. Available for all primitive types and for objects.

eq(X value, X delta)

Matches if the actual value is equal to the given value allowing the given delta. Available for float and double.

aryEq(X value)

Matches if the actual value is equal to the given value according to Arrays.equals(). Available for primitive and object arrays.

isNull(), isNull(Class clazz)

Matches if the actual value is null. Available for objects.

notNull(), notNull(Class clazz)

Matches if the actual value is not null. Available for objects.

same(X value)

Matches if the actual value is the same as the given value. Available for objects.

isA(Class clazz)

Matches if the actual value is an instance of the given class, or if it is in instance of a class that extends or implements the given class. Null always return false. Available for objects.

lt(X value), leq(X value), geq(X value), gt(X value)

Matches if the actual value is less/less or equal/greater or equal/greater than the given value. Available for all numeric primitive types and Comparable.

startsWith(String prefix), contains(String substring), endsWith(String suffix)

Matches if the actual value starts with/contains/ends with the given value. Available for Strings.

matches(String regex), find(String regex)

Matches if the actual value/a substring of the actual value matches the given regular expression. Available for Strings.

and(X first, X second)

Matches if the matchers used in first and second both match. Available for all primitive types and for objects.

or(X first, X second)

Matches if one of the matchers used in first and second match. Available for all primitive types and for objects.

not(X value)

Matches if the matcher used in value does not match.

cmpEq(X value)

Matches if the actual value is equals according to Comparable.compareTo(X o). Available for all numeric primitive types and Comparable.

cmp(X value, Comparator comparator, LogicalOperator operator)

Matches if comparator.compare(actual, value) operator 0 where the operator is <,<=,>,>= or ==. Available for objects.

capture(Capturecapture), captureXXX(Capturecapture)

Matches any value but captures it in the Capture parameter for later access. You can do and(someMatcher(...), capture(c)) to capture a parameter from a specific call to the method. You can also specify a CaptureType telling that a given Capture should keep the first, the last, all or no captured values.

 

自定义参数匹配

         例:异常equals判断eqException()

public class DemoTest {

    // ……

       @Test

       public void testCustomArgumentMatch(){

              IllegalStateException e = new IllegalStateException("Operation not allowed");

              IllegalStateException e2 = new IllegalStateException("Operation not allowed");

              // expect(mock.logThrowable(e)).andReturn(true);

              expect(mock.logThrowable(eqException(e))).andReturn(true);              replay(mock);

              mock.logThrowable(e2);

              verify(mock);

// Reusing a Mock Object

              reset(mock);

      mock.logThrowable(e2);

              verify(mock);

}

private Throwable eqException(Throwable e) {

              EasyMock.reportMatcher(new ThrowbaleEquals(e));

return null;

       }

       private class ThrowbaleEquals implements IArgumentMatcher{

              private Throwable expected;

              public ThrowbaleEquals(Throwable expected){

                     this.expected = expected;

              }

              @Override

              public boolean matches(Object actual) {

                     if(!(actual instanceof Throwable)){

                            return false;

                     }

                     String actualMessage = ((Throwable) actual).getMessage();

                     return expected.getClass().equals(actual.getClass()) &&

                                   expected.getMessage().equals(actualMessage);

spike" alt="线形标注 2(带边框和强调线): 两个接口方法定义" width=169 height=34 v:shapes="_x0000_s1036">              }

              @Override

              public void appendTo(StringBuffer buffer) {

                     buffer.append("eqException(");

                     buffer.append(expected.getClass());

                     buffer.append(" with message "");

                     buffer.append(expected.getMessage());

                     buffer.append(""");

              }

       }

 

EasyMockSupport 实现多个高仿同时欺骗

package com.spike.easymock;

import org.easymock.EasyMockSupport;

import org.junit.Before;

import org.junit.Test;

// EasyMockSupport

public class SupportTest extends EasyMockSupport {

       private ICollaborator firstCollaborator;

    private ICollaborator secondCollaborator;

    private ClassUnderTest classUnderTest;

    @Before

    public void setup() {

        classUnderTest = new ClassUnderTest();

    }

    @Test

    public void addDocument() {

        // creation phase

        firstCollaborator = createMock(ICollaborator.class);

        secondCollaborator = createMock(ICollaborator.class);

        classUnderTest.addListener(firstCollaborator);

        classUnderTest.addListener(secondCollaborator);

        // recording phase

        firstCollaborator.documentAdded("New Document");

        secondCollaborator.documentAdded("New Document");

        replayAll(); // replay all mocks at once

        // test

        classUnderTest.addDocument("New Document", new byte[0]);               

        verifyAll(); // verify all mocks at once

    }

}

 

 

Remark and miscellaneous

自定义返回值

       @SuppressWarnings({ "unchecked" })

       @Test

       public void testAnswerAndDelegate() {

              List strings = createMock(List.class);

 

              expect(strings.remove(10)).andAnswer(new IAnswer() {

                     @Override

                     public String answer() throws Throwable {

                            // System.err.println(getCurrentArguments()[0].toString());

                            String result = getCurrentArguments()[0].toString();

                            return result;

                     }

              });

             

              replay(strings);

              assertEquals(strings.remove(10), "10");

              verify(strings);

       }

 

高仿方法调用顺序EasyMock.createStrictMock()

默认的EasyMock.createMock()不检查方法调用顺序

 

使用高仿方法的stub实现

andStubReturn(Object value)

andStubThrow(Throwable throwable)

andStubAnswer(IAnsweranswer)

 asStub()

example: expect(mock.***(***)).andStubReturn(***);

不检查方法调用次数、何时调用、是否调用过

 

高仿命名

createMock(String name, Class toMock)

 createStrictMock(String name, Class toMock)

// 允许所有高仿方法调用,并返回恰当的空值(0 null false)

 createNiceMock(String name, Class toMock) 

 

高仿序列化[备注:占位符]

Mocks can be serialized at any time during their life. However, there are some obvious constraints:

  • All used matchers should be serializable (all genuine EasyMock ones are)
  • Recorded parameters should also be serializable

 

多线程[备注:占位符]

During recording, a mock is not thread-safe. So a giving mock (or mocks linked to the same IMocksControl) can only be recorded from a single thread. However, different mocks can be recorded simultaneously in different threads.

During the replay phase, mocks are by default thread-safe. This can be change for a given mock if makeThreadSafe(mock, false) is called during the recording phase. This can prevent deadlocks in some rare situations.

Finally, calling checkIsUsedInOneThread(mock, true) on a mock will make sure the mock is used in only one thread and throw an exception otherwise. This can be handy to make sure a thread-unsafe mocked object is used correctly.

OSGi [备注:占位符]

 

Partial mocking [备注:占位符]

 

Self testing [备注:占位符]

 

Replace default class instantiator [备注:占位符]

 

Class Mocking Limitations [备注:占位符]




http://blog.sina.com.cn/s/blog_9c88479d01014sam.html

posted on 2013-04-10 22:06 abin 阅读(341) 评论(0)  编辑  收藏 所属分类: easyMock

只有注册用户登录后才能发表评论。


网站导航: