EASYMOCK原理浅析

EASYMOCK原理浅析



一、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 
------------------------------------------------------------------- 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值