单元测试源码分析之一创建mock对象

之前已经介绍过Mockito和PowerMock的常见用法,PowerMock其实就是在Mockito的基础上使用了字节码技术使得其可以对静态方法,私有方法等进行插桩。

现在就先来看看Mockito是怎么创建一个mock对象的(本文的源码都是来自mockito-core-3.2.0,低版本的底层实现不是ByteBuddy)

先看下一个比较简单的Mockito例子

@Component
public class UserDao {

    public List<User> queryUserByName(String name) {
        User user = new User("1", "chenpp", 18);
        List<User> list = new ArrayList<User>();
        list.add(user);
        return list;
    }
}

public class User {
    private String id;
    private String userName;
    private Integer age;

    public User(String id, String userName, Integer age) {
        this.id = id;
        this.userName = userName;
        this.age = age;
    }
}
@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    public List<User> queryUser(String userName){
        System.out.println("查询条件userName:"+ userName);
        return userDao.queryUserByName(userName);
    }

}
@RunWith(JUnit4.class)
public class UserServiceTest {

    @InjectMocks
    private UserService userService;

    @Mock
    private UserDao userDao;

    @Before
    public void init(){
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void test(){
        List<User> list = new ArrayList<User>();
        User user1 = new User("2", "chenpp", 18);
        User user2 = new User("3", "chenpp", 18);
        list.add(user1);
        list.add(user2);
        Mockito.when(userDao.queryUserByName(eq("chenpp"))).thenReturn(list);

        List userList1 = userService.queryUser("chenpp");

        Assert.assertTrue(userList1.size() == 2);
        List userList2 = userService.queryUser("chenpp2");
        Assert.assertTrue(userList2.size() == 0);

    }

}

对于一个最基本的mock单测,一般包含如下几个部分:
1.构建被mock的对象 userDao
2.构建使用mock对象的实际对象 userService
3.建立mock对象和实际对象的关系
4.对mock对象进行插桩

明显可以看出这里的入口方法是 MockitoAnnotations.initMocks(this); ,那么看看这个方法是怎么帮我们分别构建mock对象和实际对象并将其建立联系的

MockitoAnnotations.initMocks(this)

 public static void initMocks(Object testClass) {
        if (testClass == null) {
            throw new MockitoException("testClass cannot be null. For info how to use @Mock annotations see examples in javadoc for MockitoAnnotations class");
        } else {
             //先获取注解引擎插件 -- 这里拿到的是InjectingAnnotationEngine(里面有个delegate : IndependentAnnotationEngine)
            AnnotationEngine annotationEngine = (new GlobalConfiguration()).tryGetPluginAnnotationEngine();
            //执行引擎的process方法
            annotationEngine.process(testClass.getClass(), testClass);
        }
    }

InjectingAnnotationEngine
分别执行IndependentAnnotationEngine和SpyAnnotationEngine的process方法, 这两个引擎分别对mock对象和spy对象进行实例化,然后对添加了InjectMocks注解的对象进行注入和实例化

 public void process(Class<?> clazz, Object testInstance) {
        this.processIndependentAnnotations(testInstance.getClass(), testInstance);
        this.processInjectMocks(testInstance.getClass(), testInstance);
    }
    
 private void processIndependentAnnotations(Class<?> clazz, Object testInstance) {
        //clazz是当前的测试类,从当前测试类开始向上寻找所有需要mock或者spy的对象
        for(Class classContext = clazz; classContext != Object.class; classContext = classContext.getSuperclass()) {
            //delegate : IndependentAnnotationEngine
            this.delegate.process(classContext, testInstance);
            this.spyAnnotationEngine.process(classContext, testInstance);
        }

    }
 private void processInjectMocks(Class<?> clazz, Object testInstance) {
        for(Class classContext = clazz; classContext != Object.class; classContext = classContext.getSuperclass()) {
            this.injectMocks(testInstance);
        }

    }

看下IndependentAnnotationEngine的process方法:
通过反射拿到当前clazz的所有field, 判断其上是否有@Mock注解,如果有则通过this.createMockFor(annotation,field)方法创建mock的代理对象

  public void process(Class<?> clazz, Object testInstance) {
        Field[] fields = clazz.getDeclaredFields();
        Field[] var4 = fields;
        int var5 = fields.length;
        //遍历测试类的所有field
        for(int var6 = 0; var6 < var5; ++var6) {
            Field field = var4[var6];
            boolean alreadyAssigned = false;
            Annotation[] var9 = field.getAnnotations();
            int var10 = var9.length;

            for(int var11 = 0; var11 < var10; ++var11) {
                Annotation annotation = var9[var11];
                //判断是否有添加@Captor或者@Mock注解,如果有则创建代理对象
                Object mock = this.createMockFor(annotation, field);
                if (mock != null) {
                    this.throwIfAlreadyAssigned(field, alreadyAssigned);
                    alreadyAssigned = true;
                    try {
                        //通过反射将创建的mock对象赋值给测试对象的对应属性
                        FieldSetter.setField(testInstance, field, mock);
                    } catch (Exception var15) {
                        throw new MockitoException("Problems setting field " + field.getName() + " annotated with " + annotation, var15);
                    }
                }
            }
        }

    }
    

最终通过如下代码创建Mock对象, 可以看到 Mockito.mock(type, mockSettings);方法,就是早期没有注解的时候用于创建mock对象的api

public static Object processAnnotationForMock(Mock annotation, Class<?> type, String name) {
        //创建MockSettings
        MockSettings mockSettings = Mockito.withSettings();
        if (annotation.extraInterfaces().length > 0) {
            mockSettings.extraInterfaces(annotation.extraInterfaces());
        }

        if ("".equals(annotation.name())) {
            mockSettings.name(name);
        } else {
            mockSettings.name(annotation.name());
        }

        if (annotation.serializable()) {
            mockSettings.serializable();
        }

        if (annotation.stubOnly()) {
            mockSettings.stubOnly();
        }

        if (annotation.lenient()) {
            mockSettings.lenient();
        }

        mockSettings.defaultAnswer(annotation.answer());
        //创建mock对象的核心方法
        return Mockito.mock(type, mockSettings);
    }
创建Mock对象

核心方法如下:
MockitoCore

    /**
     * @param typeToMock mock的接口或类
     * @param settings  mock对象的配置
     * */
    public <T> T mock(Class<T> typeToMock, MockSettings settings) {
        //判断settings是否是MockSettingsImpl的实例,如果不是则抛出异常
        if (!MockSettingsImpl.class.isInstance(settings)) {
            throw new IllegalArgumentException("Unexpected implementation of '" + settings.getClass().getCanonicalName() + "'\nAt the moment, you cannot provide your own implementations of that class.");
        } else {
            MockSettingsImpl impl = (MockSettingsImpl)MockSettingsImpl.class.cast(settings);
            //前面强转后校验Mock class和配置信息(底层是调用了MockSettingsImpl的validatedSettingsa方法)
            MockCreationSettings<T> creationSettings = impl.build(typeToMock);
            //[1]真正生成mock对象的方法
            T mock = MockUtil.createMock(creationSettings);
            //开始执行mock,调用各种MockCreation监听器的onMockCreated回调方法
            ThreadSafeMockingProgress.mockingProgress().mockingStarted(mock, creationSettings);
            return mock;
        }
    }

[1] 真正创建mock对象的核心方法,主要做三件事情:

  1. 创建MockHandler
  2. 创建mock对象
  3. 判断settings里是否有spy对象来确定返回spy对象还是mock对象(主要是针对@Spry注解)
    public static <T> T createMock(MockCreationSettings<T> settings) {
        //通过MockHandlerFactory创建mock的处理器
        MockHandler mockHandler =  MockHandlerFactory.createMockHandler(settings);
        //根据处理器以及settings创建mock对象
        T mock = mockMaker.createMock(settings, mockHandler);
        //获取spy对象,如果对象有spy实例,则替换掉前面生成的mock对象
        Object spiedInstance = settings.getSpiedInstance();
        if (spiedInstance != null) {
            new LenientCopyTool().copyToMock(spiedInstance, mock);
        }

        return mock;
    }
MockHandler

这里使用的是装饰器模式,先创建一个MockHandlerImpl的handler对象,然后先后用NullResultGuardian,InvocationNotifierHandler包装起来

  public static <T> MockHandler<T> createMockHandler(MockCreationSettings<T> settings) {
        //核心处理器
        MockHandler<T> handler = new MockHandlerImpl<T>(settings);
        //处理null和基本类型的返回结果
        MockHandler<T> nullResultGuardian = new NullResultGuardian<T>(handler);
        //通知监听者,进行各种方法回调
        return new InvocationNotifierHandler<T>(nullResultGuardian, settings);
    }

其作用是给每个MockCreationSettings创建其mockHandler,然后在执行mock/spy对象的方法时,都会先被handler拦截,如果有进行插桩,则执行返回插桩的结果,如果没有,则返回默认值或者真实执行结果

MockHandlerImpl

先简单分析到这里,后面再来看这部分源码

    public Object handle(Invocation invocation) throws Throwable {
        ...
        // 根据invoation查找对应stubbing
        StubbedInvocationMatcher stubbing = invocationContainer.findAnswerFor(invocation);
        if (stubbing != null) {
            //有插桩则返回插桩结果
            stubbing.captureArgumentsFrom(invocation);
            try {
                return stubbing.answer(invocation);
            } finally {
                mockingProgress().reportOngoingStubbing(ongoingStubbing);
            }
        } else {
            //没有则使用DefaultAnswer应答并返回结果值
            Object ret = mockSettings.getDefaultAnswer().answer(invocation); 
            invocationContainer.resetInvocationForPotentialStubbing(invocationMatcher);
            return ret;
        }
    }

createMock

先来看下mockMarker这个常量

    private static final MockMaker mockMaker = Plugins.getMockMaker();

    //Plugins.java
    public static MockMaker getMockMaker() {
        return registry.getMockMaker();
    }
    //PluginRegistry.java
    private final PluginSwitch pluginSwitch = new PluginLoader(new DefaultPluginSwitch())
            .loadPlugin(PluginSwitch.class);

    private final MockMaker mockMaker = new PluginLoader(pluginSwitch, DefaultMockitoPlugins.INLINE_ALIAS)
            .loadPlugin(MockMaker.class);

    MockMaker getMockMaker() {
        return mockMaker;
    }

这里的PluginSwitch只有一个方法isEnabled(String pluginClassName),表示当前插件是否可用
还有一个比较重要的急速PluginLoader对象的loadPlugin方法,看名字应该是根据class加载插件的,我们看下其实现

<PreferredType, AlternateType> Object loadPlugin(final Class<PreferredType> preferredPluginType, final Class<AlternateType> alternatePluginType) {
        try {
            //先根据Class类型通过类加载器加载对应插件
            PreferredType preferredPlugin = initializer.loadImpl(preferredPluginType);
            if (preferredPlugin != null) {
                return preferredPlugin;
            } else if (alternatePluginType != null) {
                //如果没有则尝试通过其他的PluginType加载插件
                AlternateType alternatePlugin = initializer.loadImpl(alternatePluginType);
                if (alternatePlugin != null) {
                    return alternatePlugin;
                }
            }
            //都没有的话就会使用默认的插件
            return plugins.getDefaultPlugin(preferredPluginType);
        } catch (final Throwable t) {
            //如果异常则通过jdk动态代理创建该类型插件的代理对象,不过其在执行的时候会抛出异常
            return Proxy.newProxyInstance(preferredPluginType.getClassLoader(),
                new Class<?>[]{preferredPluginType},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        throw new IllegalStateException("Could not initialize plugin: " + preferredPluginType + " (alternate: " + alternatePluginType + ")", t);
                    }
                });
        }
    }

PluginInitializer.java
使用类加载器去 **mockito-extensions/{className}**路径下查找相应的资源, 由于在Mockitio jar包的classpath下不存在mockito-extensions目录,所以都是使用默认的插件类。即:PluginSwitch使用的插件类是DefaultPluginSwitch(其isEnabled方法始终返回true,即认为所有插件都是可用的),MockMaker使用的是ByteBuddyMockMaker(使用ByteBuddy创建mock对象)

ByteBuddy是java字节码增强框架,可以动态的生成java字节码文件,比起我们自己进行字节码文件的生成,它屏蔽了底层细节,提供一套统一易上手的Api

 public <T> T loadImpl(Class<T> service) {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        if (loader == null) {
            loader = ClassLoader.getSystemClassLoader();
        }
        ...     
        resources = loader.getResources("mockito-extensions/" + service.getName());
        try {
            //findPluginClass里就根据pluginSwitch的isEnabled方法对resources进行了过滤
            String classOrAlias = new PluginFinder(pluginSwitch).findPluginClass(Iterables.toIterable(resources));
            //找到对应的PluginClass之后通过反射实例化
            if (classOrAlias != null) {
                //如果找到的class名称和指定的别名一样,就通过别名去plugins里预先加载的插件信息里获取class名称(个人感觉这里设计的太复杂了,为了获得一个class名称)
                if (classOrAlias.equals(alias)) {
                    classOrAlias = plugins.getDefaultPluginClass(alias);
                }
                Class<?> pluginClass = loader.loadClass(classOrAlias);
                //通过反射实例化
                Object plugin = pluginClass.newInstance();
                //强转
                return service.cast(plugin);
            }
            return null;
        } catch (Exception e) {
            throw new IllegalStateException(
                "Failed to load " + service + " implementation declared in " + resources, e);
        }
    }

再回到createMock这个方法,通过前面的分析知道这里的mockMaker实际是ByteBuddyMockMaker的实例,看下其实现

  T mock = mockMaker.createMock(settings, mockHandler);

ByteBuddyMockMaker.java

 private ClassCreatingMockMaker defaultByteBuddyMockMaker = new SubclassByteBuddyMockMaker();

    @Override
    public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
        //这里是SubclassByteBuddyMockMaker的实例
        return defaultByteBuddyMockMaker.createMock(settings, handler);
    }

SubclassByteBuddyMockMaker.java
可以看到,最终调用的是SubclassByteBuddyMockMaker的createMock方法,先根据settings创建mock的代理类(这里生成的就是com.ibu.itinerary.UserDao$MockitoMock$2020018071 )

 @Override
    public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
        //[1]根据settings创建mock的代理类
        Class<? extends T> mockedProxyType = createMockType(settings);
        //[2]获取Instantiator用于实例化
        Instantiator instantiator = Plugins.getInstantiatorProvider().getInstantiator(settings);
        T mockInstance = null;
        try {
            //实例化代理对象,支持构造器和Objenesis创建实例(没有特别设置的话使用的就是默认的ObjenesisInstantiator)
            mockInstance = instantiator.newInstance(mockedProxyType);
            MockAccess mockAccess = (MockAccess) mockInstance;
            //[3]使用MockMethodInterceptor设置代理类的拦截器 
            mockAccess.setMockitoInterceptor(new MockMethodInterceptor(handler, settings));
            //确认生成的mock对象与我们预期的mockType是匹配的
            return ensureMockIsAssignableToMockedType(settings, mockInstance);
        } catch (ClassCastException cce) {
          ...
        }
    }
[1] createMockType
 public SubclassByteBuddyMockMaker(SubclassLoader loader) {
        //注意这里传入的BytecodeGenerator的实例类型是SubclassBytecodeGenerator,因为使用的是只带一个参数的构造方法,matcher参数是any(),后面会使用到
        cachingMockBytecodeGenerator = new TypeCachingBytecodeGenerator(new SubclassBytecodeGenerator(loader), false);
    }
    
    @Override
    public <T> Class<? extends T> createMockType(MockCreationSettings<T> settings) {
        try {
            //根据settings构建MockFeatures,再通过TypeCachingBytecodeGenerator创建mock对象的代理类
            return cachingMockBytecodeGenerator.mockClass(MockFeatures.withMockFeatures(
                    settings.getTypeToMock(),
                    settings.getExtraInterfaces(),
                    settings.getSerializableMode(),
                    settings.isStripAnnotations()
            ));
        } catch (Exception bytecodeGenerationFailed) {
            throw prettifyFailure(settings, bytecodeGenerationFailed);
        }
    }
  

TypeCachingBytecodeGenerator.java

     public <T> Class<T> mockClass(final MockFeatures<T> params) {
        try {
            ClassLoader classLoader = params.mockedType.getClassLoader();
            //从一个typeCache的类型缓存里根据classLoader和MockitoMockKey获取mock的代理类,如果没有就创建一个
            return (Class<T>) typeCache.findOrInsert(classLoader,
                    new MockitoMockKey(params.mockedType, params.interfaces, params.serializableMode, params.stripAnnotations),
                    //这是一个匿名内部类,bytecodeGenerator实际是SubclassBytecodeGenerator的实例
                    new Callable<Class<?>>() {
                        @Override
                        public Class<?> call() throws Exception {
                            return bytecodeGenerator.mockClass(params);
                        }
                    }, BOOTSTRAP_LOCK);
        } 
        ...
    }

 //TypeCache的findOrInsert方法  一开始肯定是没有缓存的,所以调用insert方法
 public Class<?> findOrInsert(ClassLoader classLoader, T key, Callable<Class<?>> lazy) {
        Class<?> type = this.find(classLoader, key);
        if (type != null) {
            return type;
        } else {
            //在执行insert之前会先调用Callable的call方法,也就是SubclassBytecodeGenerator的mockClass方法
            return this.insert(classLoader, key, (Class)lazy.call());     
        }
    }

SubclassBytecodeGenerator主要就是使用ByteBuddy的api生成代理类,可以看到其生成的类名规则如下:

   String name = String.format("%s$%s$%d", typeName, "MockitoMock", Math.abs(random.nextInt()));
[2]getInstantiator获取构造器

和前面分析的一样,这里的Plugins.getInstantiatorProvider(),最终获取到的是默认的InstantiatorProvider2插件,也就是DefaultInstantiatorProvider的实例对象,也就是说

   Instantiator instantiator = Plugins.getInstantiatorProvider().getInstantiator(settings);

DefaultInstantiatorProvider.java

   private final static Instantiator INSTANCE = new ObjenesisInstantiator();

    public Instantiator getInstantiator(MockCreationSettings<?> settings) {
        //如果settings里有指定构造器,就使用指定的
        if (settings != null && settings.getConstructorArgs() != null) {
            return new ConstructorInstantiator(settings.getOuterClassInstance() != null, settings.getConstructorArgs());
        } else {
            //没有就使用默认的ObjenesisInstantiator构造器(底层就是使用了ObjenesisStd)
            return INSTANCE;
        }
    }
【3】MockMethodInterceptor 拦截mock对象的所有方法

前面说过拦截mock对象后执行的是MockHandlerImpl的handle方法,那么是怎么调用到这个拦截方法的呢?
先来看一下 bytebuddy 拦截过程,如下:
就是通过method和intercept 分别制定拦截的方法和拦截后返回的结果

  Class<?> dynamicType = new ByteBuddy()
                // 指定父类
                .subclass(Object.class)
                // 根据名称来匹配需要拦截的方法
                .method(ElementMatchers.named("toString"))
                // 拦截方法调用,返回固定值
                .intercept(FixedValue.value("Hello World!"))
                // 产生字节码
                .make()
                // 加载类
                .load(getClass().getClassLoader())
                // 获得Class对象
                .getLoaded();
        Assert.assertEquals("Hello World!", dynamicType.newInstance().toString());

那再去看下Mockito是怎么通过ByteBuddy构建的代理类

   private final Implementation dispatcher = to(DispatcherDefaultingToRealMethod.class);
    private final Implementation hashCode = to(MockMethodInterceptor.ForHashCode.class);
    private final Implementation equals = to(MockMethodInterceptor.ForEquals.class);
 
 DynamicType.Builder<T> builder = byteBuddy.subclass(features.mockedType)
            .name(name)
            .ignoreAlso(isGroovyMethod())
            .annotateType(features.stripAnnotations
                ? new Annotation[0]
                : features.mockedType.getAnnotations())
            .implement(new ArrayList<Type>(features.interfaces))
            //这里的matcher是any(),表示会拦截任一方法,拦截后会调用DispatcherDefaultingToRealMethod类里的方法
            .method(matcher)
            .intercept(dispatcher)
            .transform(withModifiers(SynchronizationState.PLAIN))
            .attribute(features.stripAnnotations
                ? MethodAttributeAppender.NoOp.INSTANCE
                : INCLUDING_RECEIVER)
            //设置isHashCode和isEquals方法拦截后执行的方法,分别是MockMethodInterceptor的ForHashCode和ForEquals方法
            .method(isHashCode())
            .intercept(hashCode)
            .method(isEquals())
            .intercept(equals)
            .serialVersionUid(42L)
            //设置一个MockMethodInterceptor类型的字段,并让其实现MockAccess接口,这也是前面可以将mock对象转成MockAccess对象的原因
            .defineField("mockitoInterceptor", MockMethodInterceptor.class, PRIVATE)
            .implement(MockAccess.class)
            .intercept(FieldAccessor.ofBeanProperty());

通过源码可以看出来,这里拦截mock对象的方法调用后,会将其交给mockitoInterceptor处理,调用其doIntercept方法,最终调用了其handler的handle方法,也就是一开始说的MockHandlerImpl的handle方法

 public static class DispatcherDefaultingToRealMethod {
        //我们通常调用mock方法拦截后进入的应该是interceptSuperCallable方法
        @SuppressWarnings("unused")
        @RuntimeType
        @BindingPriority(BindingPriority.DEFAULT * 2)
        public static Object interceptSuperCallable(@This Object mock,
                                                    @FieldValue("mockitoInterceptor") MockMethodInterceptor interceptor,
                                                    @Origin Method invokedMethod,
                                                    @AllArguments Object[] arguments,
                                                    @SuperCall(serializableProxy = true) Callable<?> superCall) throws Throwable {
            if (interceptor == null) {
                return superCall.call();
            }
            return interceptor.doIntercept(
                mock,
                invokedMethod,
                arguments,
                new RealMethod.FromCallable(superCall)
                                          );
        }

        @SuppressWarnings("unused")
        @RuntimeType
        public static Object interceptAbstract(@This Object mock,
                                               @FieldValue("mockitoInterceptor") MockMethodInterceptor interceptor,
                                               @StubValue Object stubValue,
                                               @Origin Method invokedMethod,
                                               @AllArguments Object[] arguments) throws Throwable {
            if (interceptor == null) {
                return stubValue;
            }
            return interceptor.doIntercept(
                mock,
                invokedMethod,
                arguments,
                RealMethod.IsIllegal.INSTANCE
                                          );
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值