手写简单版的powermock,写着玩

因为觉得powermock的运用cglib来修改字节码挺好玩的,所以这几天自己研究了下,还是那样,基于TDD来编写powermock,写这篇文章的目的是为了厘清自己的思路

首先powermock的核心就是when和thenReturn这俩东西,when的时候,其实就是打了个桩:

public static <T> OngoingStubbing<T> when(T methodCall) {
        //return Mockito.when(methodCall);
        return new OngoingStubbing<T>(methodCall);
    }

这个桩子OngoingStubbing,在thenReturn时也会用到:

public class OngoingStubbing<T> {


    //answer
    // add ans
    // add return
    //
    private final InvocationContainerImpl0 invocationContainerImpl = new InvocationContainerImpl0();


    private T method;

    public OngoingStubbing (T method) {
        this.method = method;
    }
    public OngoingStubbing<T> thenReturn(T var1) {
        invocationContainerImpl.addAnswer(new Return0(var1));
        return this;
    }
}

可以看到里面有个invocationContainerImpl,这个变量是用来干什么的呢? 其实就是为了处理thenReturn时的返回变量的,通过addAnswer方法,既然是这样,那么这些answer是在什么时候取出来的呢? 当然是在后面实际调用方法时,会取到这个Answer,所以是怎么取到的?

也是通过invocationContainerImpl的方法findAnswersForStubbing,那么findAnswersForStubbing是在什么时候被调用的呢,就是通过修改字节码来使他在callback时会被调用到,callback的实现,在powermock分为七种,我们在这里用的MethodInteceptor,一个简单的实现是:

package mockito;

import org.mockito.cglib.proxy.MethodInterceptor;
import org.mockito.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class MethodInterceptorImpl implements MethodInterceptor {

    public Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable {
        return "2";
    }
}

自己写的MockCreator用来使用这个字节码增强:

package mockito;

import org.mockito.cglib.proxy.Enhancer;

public class MockCreator {

    public static <T> T createMock(Class<T> type) {

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(type);
        // 设置回调方法 - 回调方法实现为 FixedValue (固定值) .
        enhancer.setCallback(new MethodInterceptorImpl());
        // 创建 SampleClass 的代理子类实例
        T proxy = (T) enhancer.create();
        return proxy;
    }
}

现在的一个比较简略的实现就是,我们在测试方法运行之前,把需要mock的东西mock住:

import mockito.MethodInterceptorImpl;
import mockito.PowerMockito;
import mockito.StaticClass;
import org.junit.Before;
import org.junit.Test;
import org.mockito.cglib.proxy.Enhancer;
import org.mockito.cglib.proxy.FixedValue;

import static mockito.MockCreator.createMock;
import static mockito.PowerMockito.*;
public class MockTest {


    StaticClass staticmethod;

    //把stati to mock method
    // return 时把
    //nongq inte zuolesha
    @Before
    public void setUp() throws Exception{
        staticmethod = createMock(StaticClass.class);
    }



    @Test
    public void test() {

        mockStatic(StaticClass.class);
        StaticClass sc = new StaticClass();
        when(sc.nmsl()).thenReturn("2");
        System.out.println(staticmethod.nmsl());
    }


}

但是这样的一个没有实现的点,就是thenReturn时的value没法传进MethodImpl去,所以现在最难的一步就是,如何把thenReturn("2")的"2"传进callback里面,我的办法就是pullongingstub


public class MethodInterceptorImpl
        implements MethodInterceptor {

    Object getReturnValue() {
        OngoingStubbing<String> stringOngoingStubbing = OngoingStubbing.pullongoingStub();
        Return0 return0 = (Return0)stringOngoingStubbing.findAnswersForStubbing();
        return return0.value;
    }

    public Object intercept(Object obj, Method methods, Object[] args, MethodProxy proxy) throws Throwable {
        return getReturnValue();
    }
}

为了简单,我把pullonging设置成了静态方法,并且对invocationhandler也改成了静态变量:


public class OngoingStubbing<T> {


    //answer
    // add ans
    // add return
    //findAnswersForStubbing
    public static InvocationContainerImpl0 invocationContainerImpl = new InvocationContainerImpl0();


    private T method;

    public static OngoingStubbing<String> pullongoingStub() {
        OngoingStubbing<String> stringOngoingStubbing = new OngoingStubbing<String>(invocationContainerImpl);
        return stringOngoingStubbing;

    }

这样,就能找到对应的answer了,这个answer就是thenReturn时放进去的:

public OngoingStubbing<T> thenReturn(T var1) {
        invocationContainerImpl.addAnswer(new Return0(var1));
        return this;
    }

运行可得到结果:

 不过上面的缺点就是,只能when then调用一次,只能返回第一个Answer这显然是不太好的,所以要想办法用bindmatcher,来对每个方法,返回对应的Answer,首先就要明白powermock是怎么match的,首先会存储一个matcherStack,里面存储的类似 any()和方法的键值对

 通过 argumentMatcherStorage.pullLocalizedMatchers();方法,获取已经有的matchers,然后通过setPotential,来设置可能的mather为当前已有的mathcers

 有一行,比较重要,就是

OngoingStubbingImpl<T> ongoingStubbing = new OngoingStubbingImpl(this.invocationContainer);

这个就是和上面我的静态invocationHandler的处理类似,但这个是放在mockHandler定义了这个invocation,这样说来,如果一个静态成员不该出现,那么把它放在中间处理器(mockHandler)里面作为成员变量,并作为new xxx()的入参,这算是一种解决办法。

至于

ThreadSafeMockingProgress.mockingProgress().reportOngoingStubbing(ongoingStubbing);

这一行,应该是为了线程安全,做的处理,

扩展callbackilter,可以用来多次增强并且在指定环境使用指定的方法,可参考:

实战CGLib系列之proxy篇(二):回调过滤CallbackFilter_java风云的博客-CSDN博客

我们看powermock源码的话,其实thenReturn就是addAnswer,

public OngoingStubbing<T> thenReturn(T var1) {
        invocationContainerImpl.addAnswer(new Return0(var1));
        return this;
    }

所以其实就是我们要把invocationImpl传进MethodInteceptorImpl里面,那么powermock是通过mockHandlerImpl处理的:

现在就是不知道invocationImpl是啥时候传进去mockHandlerImpl的

应该是when的时候,我们会把invocationImpl注册进mockHandler里面,不是,when的时候会生成一个ongoingstub,然后这个stub给后面使用

而且,第一次when then是,powermock会实际调用这个方法,这个时候会往stub里面放进一个桩(通过when then放进去的),等第二次调用时,就不会了,因为已经有了stub,那么通过这个stub来answer,返回之前打桩的值。

还有一个比较麻烦的点就是,如何写出七种不同的inteceptor

spy

好像是给原来的方法包裹了一层拦截器(或者代理),调用的时候实际调用的是代理,然后代理调用拦截器,再通过InterceptedInvocation的callRealMethod来调用实际方法:

public Object callRealMethod() throws Throwable {
        if (!this.realMethod.isInvokable()) {
            throw Reporter.cannotCallAbstractRealMethod();
        } else {
            return this.realMethod.invoke();
        }
    }

而实际调用的是call方法:

public static class FromCallable extends RealMethod.FromBehavior implements RealMethod {
        public FromCallable(final Callable<?> callable) {
            super(new RealMethodBehavior() {
                public Object call() throws Throwable {
                    return callable.call();
                }
            });
        }
    }

spy和mock用的都是同一套逻辑,不过spy会执行call方法,但是mock不会走进call方法里面,spy的调用如下

mock的调用栈如下,算了,mock没有调用栈...


ByteCodeFramework里面有@preparefortest注解的逻辑

在mockStatic的时候遇到了比较多的问题:

首先要读懂bytebuddy相关代码

比较核心的应该是

DynamicType,它有很多内嵌类和接口,比如Builder,而MethodDefinition继承自Builder,ReceiverTypeDefinition又继承自MethodDefinition,单独的接口ImplementationDefinition里面有个intercept方法,如下:

DynamicType.Builder.MethodDefinition.ReceiverTypeDefinition<U> intercept(Implementation var1);

AbstractBase继承自ImplementationDefinition,然后MethodMatchAdapter继承自AbstractBase,并且覆写了上面的方法:

public DynamicType.Builder.MethodDefinition.ReceiverTypeDefinition<U> intercept(Implementation implementation) {
                        return this.materialize(new ForImplementation(implementation));
                    }

不太知道materialize是干啥的

ByteBuddy使用入坑_tinysakurac的博客-CSDN博客

这个是bytebuddy的用法,关注Delegation用法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值