EASYMOCK原理浅析

来源:互联网 发布:淘宝网址怎么链接 编辑:程序博客网 时间:2024/05/17 17:55

EASYMOCK原理浅析


来源:http://shlteater.iteye.com/blog/394191

一、EASYMOCK基本工作方式回顾 
首先我们通过一个最基本的例子来回顾一下EASYMOCK的工作方式 

我们有一个计算器,里面依赖一个做int加法的加法器 
Calculator.java 
Java代码  收藏代码
  1. public class Calculator {  
  2.       
  3.     private Adder adder;  
  4.   
  5.     public void setAdder(Adder adder) {  
  6.       
  7.         this.adder = adder;  
  8.     }  
  9.       
  10.     public int add(int x, int y){  
  11.         return adder.add(x, y);  
  12.     }  
  13.   
  14. }  


Adder.java 
Java代码  收藏代码
  1. public interface Adder {  
  2.       
  3.     public int add(int x, int y);  
  4. }  


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

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

此时我们需要一个Adder接口的Mock对象 
由它来响应Calculator的方法调用 
这里我们可以实用EASYMOCK所提供的功能 
Java代码  收藏代码
  1. import org.easymock.EasyMock;  
  2. import org.junit.Before;  
  3. import org.junit.Test;  
  4.   
  5. import junit.framework.Assert;  
  6. import junit.framework.TestCase;  
  7.   
  8. public class CalculatorTest extends TestCase {  
  9.   
  10.     private Calculator tested;  
  11.   
  12.     private Adder adder;  
  13.   
  14.     @Before  
  15.     public void setUp() {  
  16.   
  17.         tested = new Calculator();  
  18.         adder = EasyMock.createMock(Adder.class);  
  19.         tested.setAdder(adder);  
  20.     }  
  21.   
  22.     @Test  
  23.     public void testAdd() {  
  24.         // adder in record state  
  25.         EasyMock.expect(adder.add(12)).andReturn(3);  
  26.         EasyMock.replay(adder);  
  27.         // adder in replay state  
  28.         Assert.assertEquals(3, tested.add(12));  
  29.     }  
  30. }  

在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 
Java代码  收藏代码
  1. public static <T> T createMock(Class<T> toMock) {  
  2.     return createControl().createMock(toMock);  
  3. }  

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

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

我们再来看看MocksControl的createMock方法 
in org.easymock.internal.MocksControl 
Java代码  收藏代码
  1. public <T> T createMock(Class<T> toMock) {  
  2.     try {  
  3.         state.assertRecordState();  
  4.         IProxyFactory<T> proxyFactory = createProxyFactory(toMock);  
  5.         return proxyFactory.createProxy(toMock, new ObjectMethodsFilter(  
  6.                 toMock, new MockInvocationHandler(this), null));  
  7.     } catch (RuntimeExceptionWrapper e) {  
  8.         throw (RuntimeException) e.getRuntimeException().fillInStackTrace();  
  9.     }  
  10. }  
  11.   
  12. protected <T> IProxyFactory<T> createProxyFactory(Class<T> toMock) {  
  13.     return new JavaProxyFactory<T>();  
  14. }  

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

接着来看一下JavaProxyFactory类的实现 
Java代码  收藏代码
  1. public class JavaProxyFactory<T> implements IProxyFactory<T> {  
  2.     @SuppressWarnings("unchecked")  
  3.     public T createProxy(Class<T> toMock, InvocationHandler handler) {  
  4.         return (T) Proxy.newProxyInstance(toMock.getClassLoader(),  
  5.                 new Class[] { toMock }, handler);  
  6.     }  
  7. }  


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

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


Java代码  收藏代码
  1. public static void main(String[] args) {  
  2.     Adder adder = new AdderDebugProxy(new AdderImpl());  
  3.     adder.add(1,2);  
  4. }  


程序输出 
1 + 2 = 3 

但是这是一个静态代理,我们的代理类必须要静态写死 
如果需要在程序运行时再生成代理类的话,就要使用JDK的动态代理功能 
由于无法直接定义代理类,我们需要借助一个 
java.lang.reflect.InvocationHandler 
来定义我们需要的代理行为 
Java代码  收藏代码
  1. public class DebugInvocationHandler implements InvocationHandler {  
  2.   
  3.     private Adder delegate;  
  4.   
  5.     public DebugInvocationHandler(Adder delegate) {  
  6.   
  7.         this.delegate = delegate;  
  8.     }  
  9.   
  10.     public Object invoke(Object proxy, Method method, Object[] args)  
  11.             throws Throwable {  
  12.   
  13.         try{  
  14.             if(method.getName().equals("add")){  
  15.                 if(null == args || args.length != 2){  
  16.                     throw new IllegalArgumentException("wrong argument length for add()");  
  17.                 }  
  18.                 Integer x = (Integer)args[0];  
  19.                 Integer y = (Integer)args[1];  
  20.                 Integer result = delegate.add(x.intValue(), y.intValue());  
  21.                 System.out.println("" + x + " + " + y + " = " + result);  
  22.                 return result.intValue();  
  23.             }  
  24.             return method.invoke(delegate, args);  
  25.         } catch(InvocationTargetException e){  
  26.             throw e;  
  27.         }  
  28.     }  
  29. }  


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

最后我们使用一个工厂类来创建它 
Java代码  收藏代码
  1. public class AdderProxyFactory {  
  2.   
  3.     public static Adder createDebugProxy(Adder delegate) {  
  4.   
  5.         return (Adder) Proxy.newProxyInstance(delegate.getClass()  
  6.                 .getClassLoader(), delegate.getClass().getInterfaces(),  
  7.                 new DebugInvocationHandler(delegate));  
  8.     }  
  9. }  


Java代码  收藏代码
  1. public static void main(String[] args) {  
  2.     Adder adder = AdderProxyFactory.createDebugProxy(new AdderImpl());  
  3.     adder.add(12);  
  4. }  

程序输出 
1 + 2 = 3 

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

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

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

in org.easymock.internal.MockInvocationHandler 
Java代码  收藏代码
  1. public final class MockInvocationHandler implements InvocationHandler, Serializable {  
  2.   
  3.     private static final long serialVersionUID = -7799769066534714634L;  
  4.       
  5.     private final MocksControl control;  
  6.   
  7.     public MockInvocationHandler(MocksControl control) {  
  8.         this.control = control;  
  9.     }  
  10.   
  11.     public Object invoke(Object proxy, Method method, Object[] args)  
  12.             throws Throwable {  
  13.         try {  
  14.             if (control.getState() instanceof RecordState) {  
  15.                 LastControl.reportLastControl(control);  
  16.             }  
  17.             return control.getState().invoke(  
  18.                     new Invocation(proxy, method, args));  
  19.         } catch (RuntimeExceptionWrapper e) {  
  20.             throw e.getRuntimeException().fillInStackTrace();  
  21.         } catch (AssertionErrorWrapper e) {  
  22.             throw e.getAssertionError().fillInStackTrace();  
  23.         } catch (ThrowableWrapper t) {  
  24.             throw t.getThrowable().fillInStackTrace();  
  25.         }  
  26.     }  
  27.   
  28.     public MocksControl getControl() {  
  29.         return control;  
  30.     }  
  31. }  



三、浅析MOCK对象的状态机制-----State模式的应用 
我们直接看上面的代码,可以看到对MOCK对象的方法调用 
直接被转化成了control.getState().invoke()的调用 
这又是怎样的实现,我们回过头来看MocksControl的代码 
Java代码  收藏代码
  1. public class MocksControl implements IMocksControl, IExpectationSetters<Object>, Serializable {  
  2.   
  3.     // .......      
  4.   
  5.     private IMocksControlState state;  
  6.   
  7.     private IMocksBehavior behavior;  
  8.   
  9.     // .......  
  10.   
  11.     public final void reset() {  
  12.         behavior = new MocksBehavior(type == MockType.NICE);  
  13.         behavior.checkOrder(type == MockType.STRICT);  
  14.         behavior.makeThreadSafe(false);  
  15.         state = new RecordState(behavior);  
  16.         LastControl.reportLastControl(null);  
  17.     }  
  18.   
  19.     // ......  
  20.   
  21.     public void replay() {  
  22.         try {  
  23.             state.replay();  
  24.             state = new ReplayState(behavior);  
  25.             LastControl.reportLastControl(null);  
  26.         } catch (RuntimeExceptionWrapper e) {  
  27.             throw (RuntimeException) e.getRuntimeException().fillInStackTrace();  
  28.         }  
  29.     }  
  30.   
  31.     // ......  
  32.   
  33.     public IExpectationSetters<Object> andReturn(Object value) {  
  34.         try {  
  35.             state.andReturn(value);  
  36.             return this;  
  37.         } catch (RuntimeExceptionWrapper e) {  
  38.             throw (RuntimeException) e.getRuntimeException().fillInStackTrace();  
  39.         }  
  40.     }  

可以看到这是一个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 
Java代码  收藏代码
  1. private final Object mock;  
  2.   
  3. private transient Method method;  
  4.   
  5. private final Object[] arguments;  

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


接着来看ExpectedInvocation类 
in org.easymock.internal.ExpectedInvocation 
Java代码  收藏代码
  1. private final Invocation invocation;  
  2.   
  3. // ......  
  4.   
  5. private final List<IArgumentMatcher> matchers;  

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

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

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

Result类实现了IAnswer接口,用来表示函数调用的返回(正常返回值或者异常抛出) 
其内部有两个工厂方法分别用来创建ThrowingAnswer和ReturningAnswer 
in org.easymock.internal.Result 
Java代码  收藏代码
  1. private IAnswer<?> value;  
  2.   
  3. private Result(IAnswer<?> value) {  
  4.     this.value = value;  
  5. }  
  6.   
  7. public static Result createThrowResult(final Throwable throwable) {  
  8.     class ThrowingAnswer implements IAnswer<Object>, Serializable {  
  9.   
  10.         private static final long serialVersionUID = -332797751209289222L;  
  11.   
  12.         public Object answer() throws Throwable {  
  13.             throw throwable;  
  14.         }  
  15.   
  16.         @Override  
  17.         public String toString() {  
  18.             return "Answer throwing " + throwable;  
  19.         }  
  20.     }  
  21.     return new Result(new ThrowingAnswer());  
  22. }  
  23.   
  24. public static Result createReturnResult(final Object value) {  
  25.     class ReturningAnswer implements IAnswer<Object>, Serializable {  
  26.   
  27.         private static final long serialVersionUID = 6973893913593916866L;  
  28.           
  29.         public Object answer() throws Throwable {  
  30.             return value;  
  31.         }  
  32.           
  33.         @Override  
  34.         public String toString() {  
  35.             return "Answer returning " + value;  
  36.         }  
  37.     }  
  38.     return new Result(new ReturningAnswer());  
  39. }  
  40.   
  41. // .......  
  42.   
  43. public Object answer() throws Throwable {  
  44.     return value.answer();  
  45. }  

这就是在RecordState中使用andReturn()和andThrow()方法将会保存的信息 
在ReplayState中,Result将会被取出,其answer()方法被调用 
in org.easymock.internal.ReplayState 
Java代码  收藏代码
  1. private Object invokeInner(Invocation invocation) throws Throwable {  
  2.     Result result = behavior.addActual(invocation);  
  3.     LastControl.pushCurrentArguments(invocation.getArguments());  
  4.     try {  
  5.         try {  
  6.             return result.answer();  
  7.         } catch (Throwable t) {  
  8.             throw new ThrowableWrapper(t);  
  9.         }  
  10.     } finally {  
  11.         LastControl.popCurrentArguments();  
  12.     }  
  13. }  

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 
Java代码  收藏代码
  1. public static <T> T createMock(Class<T> toMock) {  
  2.     return createControl().createMock(toMock);  
  3. }  
  4.   
  5. // ......  
  6.   
  7. public static IMocksControl createControl() {  
  8.     return new MocksClassControl(MocksControl.MockType.DEFAULT);  
  9. }  


而MocksClassControl是MocksControl的子类 
它继承了父类的createControl方法 
in org.easymock.internal.MocksControl 
Java代码  收藏代码
  1. public <T> T createMock(Class<T> toMock) {  
  2.     try {  
  3.         state.assertRecordState();  
  4.         IProxyFactory<T> proxyFactory = createProxyFactory(toMock);  
  5.         return proxyFactory.createProxy(toMock, new ObjectMethodsFilter(  
  6.                 toMock, new MockInvocationHandler(this), null));  
  7.     } catch (RuntimeExceptionWrapper e) {  
  8.         throw (RuntimeException) e.getRuntimeException().fillInStackTrace();  
  9.     }  
  10. }  


但是Override了createProxyFactory()方法 
in org.easymock.classextension.internal.MocksClassControl 
Java代码  收藏代码
  1. @Override  
  2. protected <T> IProxyFactory<T> createProxyFactory(Class<T> toMock) {  
  3.     if (toMock.isInterface()) {  
  4.         return super.createProxyFactory(toMock);  
  5.     }  
  6.     return new ClassProxyFactory<T>();  
  7. }  

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

-------------------------------------------------------- 
使用用我们的AdderImpl具体类 
Java代码  收藏代码
  1. public class DebugMethodIntercepter implements MethodInterceptor {  
  2.   
  3.     public Object intercept(Object obj, Method method, Object[] args,  
  4.             MethodProxy proxy) throws Throwable {  
  5.                 // 对proxy类的调用将会转化为对其父类的调用  
  6.         Object result = proxy.invokeSuper(obj, args);  
  7.         System.out.println("" + (Integer) args[0] + " + " + (Integer) args[1]  
  8.                 + " = " + (Integer) result);  
  9.         return result;  
  10.     }  
  11.   
  12. }  


Java代码  收藏代码
  1. public static void main(String[] args) {  
  2.     AdderImpl adder = createDebugProxy();  
  3.     adder.add(12);  
  4. }  
  5.   
  6. public static AdderImpl createDebugProxy() {  
  7.     Enhancer enhancer = new Enhancer();  
  8.     enhancer.setSuperclass(AdderImpl.class);  
  9.     enhancer.setCallback(new DebugMethodIntercepter());  
  10.     return (AdderImpl)enhancer.create();  
  11. }  


程序返回 
1 + 2 = 3 
------------------------------------------------------------------- 
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 Dnf与系统不兼容怎么办 使命召唤7显示w怎么办 战地3王者太卡怎么办 合金装备5消音器没了怎么办 红警基地没了怎么办 玩战地1帧数太低怎么办 战地1点游戏不开怎么办 ios耳机孔坏了怎么办? 吃泻药都不排便怎么办 上司离职了我该怎么办 我上司要辞职我怎么办 一方坚决不同意离婚我该怎么办 模拟农场车翻了怎么办 手机退出键坏了怎么办 dnf邮件发错了怎么办 手机提示sd卡已损坏怎么办 解压包文件数据损坏该怎么办 电脑被压缩后电脑打不开怎么办 眼睛里进了飞虫怎么办 虫子飞到眼睛里怎么办 云电脑pc版双鼠标怎么办 电脑蓝屏代码7f怎么办 笔记本电脑蓝屏开不了机怎么办 装xp系统后蓝屏怎么办 一键ghost断电了怎么办 如何防止cpu降频怎么办 win7 64位系统不兼容怎么办 网页无法加载打印机插件怎么办 微信提示安装了插件怎么办 电脑上不了网怎么办 win10 win10电脑突然没网了怎么办 笔记本玩战争前线发热严重怎么办 玩战争前线总是闪退怎么办 uu加速器卡在29怎么办 新ipad下载不了东西怎么办 福昕pdf阅读器找不到图章怎么办 战地4没达到分数怎么办 植物在太暗环境怎么办 玩绝地求生机型不支持怎么办 安卓安装包损坏怎么办 方舟手游安装包损坏怎么办