`
shlteater
  • 浏览: 4422 次
  • 性别: Icon_minigender_1
  • 来自: 广州
文章分类
社区版块
存档分类
最新评论

EASYMOCK原理浅析

    博客分类:
  • JAVA
阅读更多
一、EASYMOCK基本工作方式回顾
首先我们通过一个最基本的例子来回顾一下EASYMOCK的工作方式

我们有一个计算器,里面依赖一个做int加法的加法器
Calculator.java
public class Calculator {
	
	private Adder adder;

	public void setAdder(Adder adder) {
	
		this.adder = adder;
	}
	
	public int add(int x, int y){
		return adder.add(x, y);
	}

}


Adder.java
public interface Adder {
	
	public int add(int x, int y);
}


其中这个加法器的实现在别的模块中
在计算器的模块中我们调用加法器的接口

现在我们需要对Calculator进行单元测试
此时我们不想依赖Adder的具体实现来进行测试
这样Adder模块内的错误将会干扰Calculator模块的测试
造成问题定位困难

此时我们需要一个Adder接口的Mock对象
由它来响应Calculator的方法调用
这里我们可以实用EASYMOCK所提供的功能
import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;

import junit.framework.Assert;
import junit.framework.TestCase;

public class CalculatorTest extends TestCase {

	private Calculator tested;

	private Adder adder;

	@Before
	public void setUp() {

		tested = new Calculator();
		adder = EasyMock.createMock(Adder.class);
		tested.setAdder(adder);
	}

	@Test
	public void testAdd() {
		// adder in record state
		EasyMock.expect(adder.add(1, 2)).andReturn(3);
		EasyMock.replay(adder);
		// adder in replay state
		Assert.assertEquals(3, tested.add(1, 2));
	}
}

在setUp()中我们通过EasyMock.createMock()方法生成了一个MOCK对象
并且注入到Calculator的实例中
现在MOCK对象处于Record State下
在这个状态下,通过对MOCK对象进行方法调用
以及对EasyMock.expect() /andReturn() / andThrow()
方法的调用,我们能够记录MOCK对象的预期行为
这些行为将在Replay State中进行回放

比如上例,我们在adder对象的Record State下记录了一次
adder.add()调用,参数为1和2,返回值为3
接着在Replay State下,通过调用Calculator.add()方法
我们调用了adder对象的add()方法
EasyMock将会检查这次调用的方法和参数列表是否与已经保存的调用一致
如果一致的话,返回所保存的返回值
由于这次调用和Record State中的记录一致
上面例子中我们的test.add()将会返回3

二、MOCK对象的创建------JDK的动态代理
接下来我想通过EasyMock的源码来窥探一下Mock对象的创建过程
这条语句
adder = EasyMock.createMock(Adder.class);

调用了
in org.easymock.EasyMock
    public static <T> T createMock(Class<T> toMock) {
        return createControl().createMock(toMock);
    }

我们可以看到这里创建了一个控制器MocksControl
然后调用了MocksControl的createMock方法
in org.easymock.EasyMock
    public static IMocksControl createControl() {
        return new MocksControl(MocksControl.MockType.DEFAULT);
    }

这个MocksControl类用于管理Mock对象的状态迁移
即Record State和Replay State的转换

我们再来看看MocksControl的createMock方法
in org.easymock.internal.MocksControl
    public <T> T createMock(Class<T> toMock) {
        try {
            state.assertRecordState();
            IProxyFactory<T> proxyFactory = createProxyFactory(toMock);
            return proxyFactory.createProxy(toMock, new ObjectMethodsFilter(
                    toMock, new MockInvocationHandler(this), null));
        } catch (RuntimeExceptionWrapper e) {
            throw (RuntimeException) e.getRuntimeException().fillInStackTrace();
        }
    }

    protected <T> IProxyFactory<T> createProxyFactory(Class<T> toMock) {
        return new JavaProxyFactory<T>();
    }

我们看到这里创建了一个代理类工厂
然后使用代理类工厂创建了Mock对象

接着来看一下JavaProxyFactory类的实现
public class JavaProxyFactory<T> implements IProxyFactory<T> {
    @SuppressWarnings("unchecked")
    public T createProxy(Class<T> toMock, InvocationHandler handler) {
        return (T) Proxy.newProxyInstance(toMock.getClassLoader(),
                new Class[] { toMock }, handler);
    }
}


这里使用了JDK中的java.lang.reflect.Proxy类来实现动态代理类的创建
-------------------------------------------------------
关于JDK的动态代理这里补充一个简单的例子给不太熟悉的同学
就从我们开始的Adder接口说起
补充一个实现类
public class AdderImpl implements Adder{
	
	public int add(int x, int y){
		return x + y;
	}
}

现在我想实现一个DEBUG功能,就是在执行add()方法的时候
能在控制台输出一行 "1 + 2 = 3"
使用Proxy模式,我们可以这样实现
public class AdderDebugProxy implements Adder{
	
	private Adder delegate;
	
	public AdderDebugProxy(Adder delegate){
		this.delegate = delegate;
	}
	
	public int add(int x, int y){
		int result = delegate.add(x, y);
		System.out.println("" + x + " + " + y + " = " + result);
		return result;
	}
}


	public static void main(String[] args) {
		Adder adder = new AdderDebugProxy(new AdderImpl());
		adder.add(1,2);
	}


程序输出
1 + 2 = 3

但是这是一个静态代理,我们的代理类必须要静态写死
如果需要在程序运行时再生成代理类的话,就要使用JDK的动态代理功能
由于无法直接定义代理类,我们需要借助一个
java.lang.reflect.InvocationHandler
来定义我们需要的代理行为
public class DebugInvocationHandler implements InvocationHandler {

	private Adder delegate;

	public DebugInvocationHandler(Adder delegate) {

		this.delegate = delegate;
	}

	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {

		try{
			if(method.getName().equals("add")){
				if(null == args || args.length != 2){
					throw new IllegalArgumentException("wrong argument length for add()");
				}
				Integer x = (Integer)args[0];
				Integer y = (Integer)args[1];
				Integer result = delegate.add(x.intValue(), y.intValue());
				System.out.println("" + x + " + " + y + " = " + result);
				return result.intValue();
			}
			return method.invoke(delegate, args);
		} catch(InvocationTargetException e){
			throw e;
		}
	}
}


在实际使用的时候,对于动态生成的Proxy类
调用proxy.add(int x, int y)
将会被封装成对InvocationHandler的调用
invoke(proxy, method, args)
其中method为add方法
args为封装x和y的Object数组

最后我们使用一个工厂类来创建它
public class AdderProxyFactory {

	public static Adder createDebugProxy(Adder delegate) {

		return (Adder) Proxy.newProxyInstance(delegate.getClass()
				.getClassLoader(), delegate.getClass().getInterfaces(),
				new DebugInvocationHandler(delegate));
	}
}


	public static void main(String[] args) {
		Adder adder = AdderProxyFactory.createDebugProxy(new AdderImpl());
		adder.add(1, 2);
	}

程序输出
1 + 2 = 3

-------------------------------------------------------

我们回过头来看EasyMock的源码
            return proxyFactory.createProxy(toMock, new ObjectMethodsFilter(
                    toMock, new MockInvocationHandler(this), null));

可以看到传入了两个参数,一个被MOCK的接口
一个ObjectMethodsFilter
这个ObjectMethodsFilter正如其名
做了一个中间层,起到了过滤Object中3个方法
equals() toString() hashCode()的作用
实际起作用的是MockInvocationHandler
而传入的this参数则间接的将MocksControl的引用传给了它所创建的MOCK对象

in org.easymock.internal.MockInvocationHandler
public final class MockInvocationHandler implements InvocationHandler, Serializable {

    private static final long serialVersionUID = -7799769066534714634L;
    
    private final MocksControl control;

    public MockInvocationHandler(MocksControl control) {
        this.control = control;
    }

    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        try {
            if (control.getState() instanceof RecordState) {
                LastControl.reportLastControl(control);
            }
            return control.getState().invoke(
                    new Invocation(proxy, method, args));
        } catch (RuntimeExceptionWrapper e) {
            throw e.getRuntimeException().fillInStackTrace();
        } catch (AssertionErrorWrapper e) {
            throw e.getAssertionError().fillInStackTrace();
        } catch (ThrowableWrapper t) {
            throw t.getThrowable().fillInStackTrace();
        }
    }

    public MocksControl getControl() {
        return control;
    }
}



三、浅析MOCK对象的状态机制-----State模式的应用
我们直接看上面的代码,可以看到对MOCK对象的方法调用
直接被转化成了control.getState().invoke()的调用
这又是怎样的实现,我们回过头来看MocksControl的代码
public class MocksControl implements IMocksControl, IExpectationSetters<Object>, Serializable {

    // .......    

    private IMocksControlState state;

    private IMocksBehavior behavior;

    // .......

    public final void reset() {
        behavior = new MocksBehavior(type == MockType.NICE);
        behavior.checkOrder(type == MockType.STRICT);
        behavior.makeThreadSafe(false);
        state = new RecordState(behavior);
        LastControl.reportLastControl(null);
    }

    // ......

    public void replay() {
        try {
            state.replay();
            state = new ReplayState(behavior);
            LastControl.reportLastControl(null);
        } catch (RuntimeExceptionWrapper e) {
            throw (RuntimeException) e.getRuntimeException().fillInStackTrace();
        }
    }

    // ......

    public IExpectationSetters<Object> andReturn(Object value) {
        try {
            state.andReturn(value);
            return this;
        } catch (RuntimeExceptionWrapper e) {
            throw (RuntimeException) e.getRuntimeException().fillInStackTrace();
        }
    }

可以看到这是一个State模式的应用
MocksControl中保存了一个IMocksControlState的实例对象
IMocksControlState接口有两个实现类,正是RecordState和ReplayState,定义了不同的操作
而MocksControl类使用reset()和replay()实现状态的迁移
而一些其他操作,则由MocksControl对外提供接口,交由State实现

而IMocksBehavior则是MockControl对象的数据模型
保存了RecordState中储存的调用
以供ReplayState取用

四、浅析EASYMOCK的数据模型
接着我们进入MocksBehavior看一看EasyMock的数据模型
一路找下去有很多的层次,最后找到几个核心类:
org.easymock.internal.ExpectedInvocation 
org.easymock.internal.Invocation
org.easymock.IArgumentMatcher
org.easymock.internal.Result

首先我们来看Invocation类
in org.easymock.internal.Invocation
    private final Object mock;

    private transient Method method;

    private final Object[] arguments;

这个类有3个属性:MOCK对象、函数和参数列表,用于保存一次对MOCK对象的调用信息
在MockInvocationHandler中,方法调用被包含为一个Invocation类的参数传给
State对象的invoke()方法
            return control.getState().invoke(
                    new Invocation(proxy, method, args));


接着来看ExpectedInvocation类
in org.easymock.internal.ExpectedInvocation
    private final Invocation invocation;

    // ......

    private final List<IArgumentMatcher> matchers;

这个类保存了一个调用信息和一系列ArgumentMatcher
这就是在RecordState中保存的调用信息
在ReplayState中MOCK对象接受方法调用
将会产生一个actual的Invocation对象
利用ExpectedInvocation类的matches()方法,EASYMOCK将匹配这个actual对象和原来记录的调用对象
in org.easymock.internal.ExpectedInvocation
    public boolean matches(Invocation actual) {
        return matchers != null ? this.invocation.getMock().equals(
                actual.getMock())
                && this.invocation.getMethod().equals(actual.getMethod())
                && matches(actual.getArguments()) : this.invocation.matches(
                actual, matcher);
    }

在这个函数中,matchers被用来比较两个调用的参数列表

默认的Matcher为org.easymock.internal.matchers.Equals
这个Matcher使用equals()方法来比较两个参数
在这个包下,EasyMock还定义了很多Matcher给使用者方便的使用
如果用户觉得不够够用的话,还可以自己来实现IArgumentMatcher

Result类实现了IAnswer接口,用来表示函数调用的返回(正常返回值或者异常抛出)
其内部有两个工厂方法分别用来创建ThrowingAnswer和ReturningAnswer
in org.easymock.internal.Result
    private IAnswer<?> value;

    private Result(IAnswer<?> value) {
        this.value = value;
    }

    public static Result createThrowResult(final Throwable throwable) {
        class ThrowingAnswer implements IAnswer<Object>, Serializable {

            private static final long serialVersionUID = -332797751209289222L;

            public Object answer() throws Throwable {
                throw throwable;
            }

            @Override
            public String toString() {
                return "Answer throwing " + throwable;
            }
        }
        return new Result(new ThrowingAnswer());
    }

    public static Result createReturnResult(final Object value) {
        class ReturningAnswer implements IAnswer<Object>, Serializable {

            private static final long serialVersionUID = 6973893913593916866L;
            
            public Object answer() throws Throwable {
                return value;
            }
            
            @Override
            public String toString() {
                return "Answer returning " + value;
            }
        }
        return new Result(new ReturningAnswer());
    }

    // .......

    public Object answer() throws Throwable {
        return value.answer();
    }

这就是在RecordState中使用andReturn()和andThrow()方法将会保存的信息
在ReplayState中,Result将会被取出,其answer()方法被调用
in org.easymock.internal.ReplayState
    private Object invokeInner(Invocation invocation) throws Throwable {
        Result result = behavior.addActual(invocation);
        LastControl.pushCurrentArguments(invocation.getArguments());
        try {
            try {
                return result.answer();
            } catch (Throwable t) {
                throw new ThrowableWrapper(t);
            }
        } finally {
            LastControl.popCurrentArguments();
        }
    }

Mock对象则会返回我们需要的值,或者抛出我们需要的异常

五、EASYMOCK Class extension的MOCK对象创建-----CGLIB动态代理
继续回顾EASYMOCK的使用
如果我们的Adder做一个小修改,现在不是接口了,是实现类或者虚基类
那么org.easymock.EasyMock.createMock()就不能使用了
因为JDK的动态代理不能生成具体类的代理
这里就需要使用org.easymock.classextension.EasyMock.createMock()来创建代理类
而这里面使用的方法就是CGLIB的Enhancer字节码增强

in org.easymock.classextension.EasyMock
    public static <T> T createMock(Class<T> toMock) {
        return createControl().createMock(toMock);
    }

    // ......

    public static IMocksControl createControl() {
        return new MocksClassControl(MocksControl.MockType.DEFAULT);
    }


而MocksClassControl是MocksControl的子类
它继承了父类的createControl方法
in org.easymock.internal.MocksControl
    public <T> T createMock(Class<T> toMock) {
        try {
            state.assertRecordState();
            IProxyFactory<T> proxyFactory = createProxyFactory(toMock);
            return proxyFactory.createProxy(toMock, new ObjectMethodsFilter(
                    toMock, new MockInvocationHandler(this), null));
        } catch (RuntimeExceptionWrapper e) {
            throw (RuntimeException) e.getRuntimeException().fillInStackTrace();
        }
    }


但是Override了createProxyFactory()方法
in org.easymock.classextension.internal.MocksClassControl
    @Override
    protected <T> IProxyFactory<T> createProxyFactory(Class<T> toMock) {
        if (toMock.isInterface()) {
            return super.createProxyFactory(toMock);
        }
        return new ClassProxyFactory<T>();
    }

对于实际的类,它返回ClassProxyFactory
而ClassProxyFactory正是使用了CGLIB来创建代理类
这里再附一个CGLIB的简单例子,在ClassProxyFactory也能找到相类似的Proxy创建代码

--------------------------------------------------------
使用用我们的AdderImpl具体类
public class DebugMethodIntercepter implements MethodInterceptor {

	public Object intercept(Object obj, Method method, Object[] args,
			MethodProxy proxy) throws Throwable {
                // 对proxy类的调用将会转化为对其父类的调用
		Object result = proxy.invokeSuper(obj, args);
		System.out.println("" + (Integer) args[0] + " + " + (Integer) args[1]
				+ " = " + (Integer) result);
		return result;
	}

}


	public static void main(String[] args) {
		AdderImpl adder = createDebugProxy();
		adder.add(1, 2);
	}
	
	public static AdderImpl createDebugProxy() {
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(AdderImpl.class);
		enhancer.setCallback(new DebugMethodIntercepter());
		return (AdderImpl)enhancer.create();
	}


程序返回
1 + 2 = 3
-------------------------------------------------------------------

分享到:
评论
1 楼 numen_wlm 2009-10-26  
写的不错,有所斩获

相关推荐

    EasyMock 使用方法与原理剖析

    EasyMock单元测试的扩展; EasyMock简介(抽象类接口做测试); EasyMock来进行测试; EasyMock如何打桩; EasyMock实践指南; EasyMock使用技巧; EasyMock使用简明手册; EasyMock使用说明; EasyMock使用手记; 用Mock ...

    easyMock

    easyMock

    easymock的使用,含demo

    本文将对 EasyMock 的功能和原理进行介绍,并通过示例来说明如何使用 EasyMock 进行单元测试。 Mock 方法是单元测试中常见的一种技术,它的主要作用是模拟一些在应用中不容易构造或者比较复杂的对象,从而把测试与...

    模拟测试辅助工具easyMock.zip

    EasyMock 是一套通过简单的方法对于指定的接口或类生成 Mock 对象的类库,它能利用对接口或类的模拟来辅助...本文将向您展示如何使用 EasyMock 进行单元测试,并对 EasyMock 的原理进行分析。 标签:easyMock

    easymock-3.2.jar

    EasyMock主要是为测试提供模拟数据,比如你可以模拟HttpServletRequest。

    EasyMock 使用方法与原理剖析.rar

    EasyMock 使用方EEasyMock 使用方法与原理剖析.rar

    easyMock2.2.doc

    easyMock2.2

    easymock资料和源代码实例

    里面有easymock的基础知识和原理分析,还有easymock的实例代码。

    easymock.jar,easymockclassextension.jar

    还在为找不到jar文件烦心吗,不用了到我空间来有你想要的,持续更新。。。 easymock.jar,easymockclassextension.jar

    easymock2.4+EasyMock使用简明手册.pdf

    easymock2 EasyMock使用简明手册

    EasyMock 3.1相关jar(所有)

    还在为EasyMock使用时出异常而烦恼? 本压缩包包含除了Junit4之外easyMock3.1所用到的所有相关jar包,junit4可自己导入eclipse自带的即可 本压缩包包括: asm.jar cglib.jar objenesis.jar等 其中asm与cglib已兼容,放心...

    EasyMock

    NULL 博文链接:https://bruceliu02.iteye.com/blog/1775446

    easymock 的使用方法简介

    easymock 的使用方法简介easymock 的使用方法简介

    easymock-3.1.jar

    easymock需要用到的包,没有它不行,easymock-3.1.jar

    easymockclassextension-3.0.jar

    easymockclassextension-3.0.jar org.easymock.classextension.EasyMock.

    easymock-4.2.jar

    EasyMock 是一套通过简单的方法对于指定的接口或类生成 Mock 对象的类库,它能利用对接口或类的模拟来辅助单元测试。 Mock 方法是单元测试中常见的一种技术,它的主要作用是模拟一些在应用中不容易构造或者比较复杂...

    EasyMock的安装与部署所需全部文件.zip

    windows环境部署easymock,有用到的全部文件和安装说明,包换node.js,redis,mongdb,easymock源码

Global site tag (gtag.js) - Google Analytics