EASYMOCK原理浅析

xiaoxiao2026-06-09  10

[b]一、EASYMOCK基本工作方式回顾[/b] 首先我们通过一个最基本的例子来回顾一下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 [b]二、MOCK对象的创建------JDK的动态代理[/b] 接下来我想通过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; }} [b]三、浅析MOCK对象的状态机制-----State模式的应用[/b] 我们直接看上面的代码,可以看到对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取用 [b]四、浅析EASYMOCK的数据模型[/b] 接着我们进入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对象则会返回我们需要的值,或者抛出我们需要的异常 [b]五、EASYMOCK Class extension的MOCK对象创建-----CGLIB动态代理[/b] 继续回顾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 -------------------------------------------------------------------
转载请注明原文地址: https://www.6miu.com/read-5049878.html

最新回复(0)