日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發(fā)布時(shí)間:2024/4/11 编程问答 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 单元测试源码分析之一创建mock对象 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

之前已經(jīng)介紹過Mockito和PowerMock的常見用法,PowerMock其實(shí)就是在Mockito的基礎(chǔ)上使用了字節(jié)碼技術(shù)使得其可以對靜態(tài)方法,私有方法等進(jìn)行插樁。

現(xiàn)在就先來看看Mockito是怎么創(chuàng)建一個(gè)mock對象的(本文的源碼都是來自mockito-core-3.2.0,低版本的底層實(shí)現(xiàn)不是ByteBuddy)

先看下一個(gè)比較簡單的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 {@Autowiredprivate UserDao userDao;public List<User> queryUser(String userName){System.out.println("查詢條件userName:"+ userName);return userDao.queryUserByName(userName);}} @RunWith(JUnit4.class) public class UserServiceTest {@InjectMocksprivate UserService userService;@Mockprivate UserDao userDao;@Beforepublic void init(){MockitoAnnotations.initMocks(this);}@Testpublic 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);}}

對于一個(gè)最基本的mock單測,一般包含如下幾個(gè)部分:
1.構(gòu)建被mock的對象 userDao
2.構(gòu)建使用mock對象的實(shí)際對象 userService
3.建立mock對象和實(shí)際對象的關(guān)系
4.對mock對象進(jìn)行插樁

明顯可以看出這里的入口方法是 MockitoAnnotations.initMocks(this); ,那么看看這個(gè)方法是怎么幫我們分別構(gòu)建mock對象和實(shí)際對象并將其建立聯(lián)系的

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(里面有個(gè)delegate : IndependentAnnotationEngine)AnnotationEngine annotationEngine = (new GlobalConfiguration()).tryGetPluginAnnotationEngine();//執(zhí)行引擎的process方法annotationEngine.process(testClass.getClass(), testClass);}}

InjectingAnnotationEngine
分別執(zhí)行IndependentAnnotationEngine和SpyAnnotationEngine的process方法, 這兩個(gè)引擎分別對mock對象和spy對象進(jìn)行實(shí)例化,然后對添加了InjectMocks注解的對象進(jìn)行注入和實(shí)例化

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是當(dāng)前的測試類,從當(dāng)前測試類開始向上尋找所有需要mock或者spy的對象for(Class classContext = clazz; classContext != Object.class; classContext = classContext.getSuperclass()) {//delegate : IndependentAnnotationEnginethis.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方法:
通過反射拿到當(dāng)前clazz的所有field, 判斷其上是否有@Mock注解,如果有則通過this.createMockFor(annotation,field)方法創(chuàng)建mock的代理對象

public void process(Class<?> clazz, Object testInstance) {Field[] fields = clazz.getDeclaredFields();Field[] var4 = fields;int var5 = fields.length;//遍歷測試類的所有fieldfor(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注解,如果有則創(chuàng)建代理對象Object mock = this.createMockFor(annotation, field);if (mock != null) {this.throwIfAlreadyAssigned(field, alreadyAssigned);alreadyAssigned = true;try {//通過反射將創(chuàng)建的mock對象賦值給測試對象的對應(yīng)屬性FieldSetter.setField(testInstance, field, mock);} catch (Exception var15) {throw new MockitoException("Problems setting field " + field.getName() + " annotated with " + annotation, var15);}}}}}

最終通過如下代碼創(chuàng)建Mock對象, 可以看到 Mockito.mock(type, mockSettings);方法,就是早期沒有注解的時(shí)候用于創(chuàng)建mock對象的api

public static Object processAnnotationForMock(Mock annotation, Class<?> type, String name) {//創(chuàng)建MockSettingsMockSettings 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());//創(chuàng)建mock對象的核心方法return Mockito.mock(type, mockSettings);}

創(chuàng)建Mock對象

核心方法如下:
MockitoCore

/*** @param typeToMock mock的接口或類* @param settings mock對象的配置* */public <T> T mock(Class<T> typeToMock, MockSettings settings) {//判斷settings是否是MockSettingsImpl的實(shí)例,如果不是則拋出異常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);//前面強(qiáng)轉(zhuǎn)后校驗(yàn)Mock class和配置信息(底層是調(diào)用了MockSettingsImpl的validatedSettingsa方法)MockCreationSettings<T> creationSettings = impl.build(typeToMock);//[1]真正生成mock對象的方法T mock = MockUtil.createMock(creationSettings);//開始執(zhí)行mock,調(diào)用各種MockCreation監(jiān)聽器的onMockCreated回調(diào)方法ThreadSafeMockingProgress.mockingProgress().mockingStarted(mock, creationSettings);return mock;}}

[1] 真正創(chuàng)建mock對象的核心方法,主要做三件事情:

  • 創(chuàng)建MockHandler
  • 創(chuàng)建mock對象
  • 判斷settings里是否有spy對象來確定返回spy對象還是mock對象(主要是針對@Spry注解)
  • public static <T> T createMock(MockCreationSettings<T> settings) {//通過MockHandlerFactory創(chuàng)建mock的處理器MockHandler mockHandler = MockHandlerFactory.createMockHandler(settings);//根據(jù)處理器以及settings創(chuàng)建mock對象T mock = mockMaker.createMock(settings, mockHandler);//獲取spy對象,如果對象有spy實(shí)例,則替換掉前面生成的mock對象Object spiedInstance = settings.getSpiedInstance();if (spiedInstance != null) {new LenientCopyTool().copyToMock(spiedInstance, mock);}return mock;}
    MockHandler

    這里使用的是裝飾器模式,先創(chuàng)建一個(gè)MockHandlerImpl的handler對象,然后先后用NullResultGuardian,InvocationNotifierHandler包裝起來

    public static <T> MockHandler<T> createMockHandler(MockCreationSettings<T> settings) {//核心處理器MockHandler<T> handler = new MockHandlerImpl<T>(settings);//處理null和基本類型的返回結(jié)果MockHandler<T> nullResultGuardian = new NullResultGuardian<T>(handler);//通知監(jiān)聽者,進(jìn)行各種方法回調(diào)return new InvocationNotifierHandler<T>(nullResultGuardian, settings);}

    其作用是給每個(gè)MockCreationSettings創(chuàng)建其mockHandler,然后在執(zhí)行mock/spy對象的方法時(shí),都會先被handler攔截,如果有進(jìn)行插樁,則執(zhí)行返回插樁的結(jié)果,如果沒有,則返回默認(rèn)值或者真實(shí)執(zhí)行結(jié)果

    MockHandlerImpl

    先簡單分析到這里,后面再來看這部分源碼

    public Object handle(Invocation invocation) throws Throwable {...// 根據(jù)invoation查找對應(yīng)stubbingStubbedInvocationMatcher stubbing = invocationContainer.findAnswerFor(invocation);if (stubbing != null) {//有插樁則返回插樁結(jié)果stubbing.captureArgumentsFrom(invocation);try {return stubbing.answer(invocation);} finally {mockingProgress().reportOngoingStubbing(ongoingStubbing);}} else {//沒有則使用DefaultAnswer應(yīng)答并返回結(jié)果值Object ret = mockSettings.getDefaultAnswer().answer(invocation); invocationContainer.resetInvocationForPotentialStubbing(invocationMatcher);return ret;}}
    createMock

    先來看下mockMarker這個(gè)常量

    private static final MockMaker mockMaker = Plugins.getMockMaker();//Plugins.javapublic static MockMaker getMockMaker() {return registry.getMockMaker();}//PluginRegistry.javaprivate 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只有一個(gè)方法isEnabled(String pluginClassName),表示當(dāng)前插件是否可用
    還有一個(gè)比較重要的急速PluginLoader對象的loadPlugin方法,看名字應(yīng)該是根據(jù)class加載插件的,我們看下其實(shí)現(xiàn)

    <PreferredType, AlternateType> Object loadPlugin(final Class<PreferredType> preferredPluginType, final Class<AlternateType> alternatePluginType) {try {//先根據(jù)Class類型通過類加載器加載對應(yīng)插件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;}}//都沒有的話就會使用默認(rèn)的插件return plugins.getDefaultPlugin(preferredPluginType);} catch (final Throwable t) {//如果異常則通過jdk動態(tài)代理創(chuàng)建該類型插件的代理對象,不過其在執(zhí)行的時(shí)候會拋出異常return Proxy.newProxyInstance(preferredPluginType.getClassLoader(),new Class<?>[]{preferredPluginType},new InvocationHandler() {@Overridepublic 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}**路徑下查找相應(yīng)的資源, 由于在Mockitio jar包的classpath下不存在mockito-extensions目錄,所以都是使用默認(rèn)的插件類。即:PluginSwitch使用的插件類是DefaultPluginSwitch(其isEnabled方法始終返回true,即認(rèn)為所有插件都是可用的),MockMaker使用的是ByteBuddyMockMaker(使用ByteBuddy創(chuàng)建mock對象)

    ByteBuddy是java字節(jié)碼增強(qiáng)框架,可以動態(tài)的生成java字節(jié)碼文件,比起我們自己進(jìn)行字節(jié)碼文件的生成,它屏蔽了底層細(xì)節(jié),提供一套統(tǒng)一易上手的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里就根據(jù)pluginSwitch的isEnabled方法對resources進(jìn)行了過濾String classOrAlias = new PluginFinder(pluginSwitch).findPluginClass(Iterables.toIterable(resources));//找到對應(yīng)的PluginClass之后通過反射實(shí)例化if (classOrAlias != null) {//如果找到的class名稱和指定的別名一樣,就通過別名去plugins里預(yù)先加載的插件信息里獲取class名稱(個(gè)人感覺這里設(shè)計(jì)的太復(fù)雜了,為了獲得一個(gè)class名稱)if (classOrAlias.equals(alias)) {classOrAlias = plugins.getDefaultPluginClass(alias);}Class<?> pluginClass = loader.loadClass(classOrAlias);//通過反射實(shí)例化Object plugin = pluginClass.newInstance();//強(qiáng)轉(zhuǎn)return service.cast(plugin);}return null;} catch (Exception e) {throw new IllegalStateException("Failed to load " + service + " implementation declared in " + resources, e);}}

    再回到createMock這個(gè)方法,通過前面的分析知道這里的mockMaker實(shí)際是ByteBuddyMockMaker的實(shí)例,看下其實(shí)現(xiàn)

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

    ByteBuddyMockMaker.java

    private ClassCreatingMockMaker defaultByteBuddyMockMaker = new SubclassByteBuddyMockMaker();@Overridepublic <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {//這里是SubclassByteBuddyMockMaker的實(shí)例return defaultByteBuddyMockMaker.createMock(settings, handler);}

    SubclassByteBuddyMockMaker.java
    可以看到,最終調(diào)用的是SubclassByteBuddyMockMaker的createMock方法,先根據(jù)settings創(chuàng)建mock的代理類(這里生成的就是com.ibu.itinerary.UserDao$MockitoMock$2020018071 )

    @Overridepublic <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {//[1]根據(jù)settings創(chuàng)建mock的代理類Class<? extends T> mockedProxyType = createMockType(settings);//[2]獲取Instantiator用于實(shí)例化Instantiator instantiator = Plugins.getInstantiatorProvider().getInstantiator(settings);T mockInstance = null;try {//實(shí)例化代理對象,支持構(gòu)造器和Objenesis創(chuàng)建實(shí)例(沒有特別設(shè)置的話使用的就是默認(rèn)的ObjenesisInstantiator)mockInstance = instantiator.newInstance(mockedProxyType);MockAccess mockAccess = (MockAccess) mockInstance;//[3]使用MockMethodInterceptor設(shè)置代理類的攔截器 mockAccess.setMockitoInterceptor(new MockMethodInterceptor(handler, settings));//確認(rèn)生成的mock對象與我們預(yù)期的mockType是匹配的return ensureMockIsAssignableToMockedType(settings, mockInstance);} catch (ClassCastException cce) {...}}
    [1] createMockType
    public SubclassByteBuddyMockMaker(SubclassLoader loader) {//注意這里傳入的BytecodeGenerator的實(shí)例類型是SubclassBytecodeGenerator,因?yàn)槭褂玫氖侵粠б粋€(gè)參數(shù)的構(gòu)造方法,matcher參數(shù)是any(),后面會使用到cachingMockBytecodeGenerator = new TypeCachingBytecodeGenerator(new SubclassBytecodeGenerator(loader), false);}@Overridepublic <T> Class<? extends T> createMockType(MockCreationSettings<T> settings) {try {//根據(jù)settings構(gòu)建MockFeatures,再通過TypeCachingBytecodeGenerator創(chuàng)建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();//從一個(gè)typeCache的類型緩存里根據(jù)classLoader和MockitoMockKey獲取mock的代理類,如果沒有就創(chuàng)建一個(gè)return (Class<T>) typeCache.findOrInsert(classLoader,new MockitoMockKey(params.mockedType, params.interfaces, params.serializableMode, params.stripAnnotations),//這是一個(gè)匿名內(nèi)部類,bytecodeGenerator實(shí)際是SubclassBytecodeGenerator的實(shí)例new Callable<Class<?>>() {@Overridepublic Class<?> call() throws Exception {return bytecodeGenerator.mockClass(params);}}, BOOTSTRAP_LOCK);} ...}//TypeCache的findOrInsert方法 一開始肯定是沒有緩存的,所以調(diào)用insert方法public Class<?> findOrInsert(ClassLoader classLoader, T key, Callable<Class<?>> lazy) {Class<?> type = this.find(classLoader, key);if (type != null) {return type;} else {//在執(zhí)行insert之前會先調(diào)用Callable的call方法,也就是SubclassBytecodeGenerator的mockClass方法return this.insert(classLoader, key, (Class)lazy.call()); }}

    SubclassBytecodeGenerator主要就是使用ByteBuddy的api生成代理類,可以看到其生成的類名規(guī)則如下:

    String name = String.format("%s$%s$%d", typeName, "MockitoMock", Math.abs(random.nextInt()));
    [2]getInstantiator獲取構(gòu)造器

    和前面分析的一樣,這里的Plugins.getInstantiatorProvider(),最終獲取到的是默認(rèn)的InstantiatorProvider2插件,也就是DefaultInstantiatorProvider的實(shí)例對象,也就是說

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

    DefaultInstantiatorProvider.java

    private final static Instantiator INSTANCE = new ObjenesisInstantiator();public Instantiator getInstantiator(MockCreationSettings<?> settings) {//如果settings里有指定構(gòu)造器,就使用指定的if (settings != null && settings.getConstructorArgs() != null) {return new ConstructorInstantiator(settings.getOuterClassInstance() != null, settings.getConstructorArgs());} else {//沒有就使用默認(rèn)的ObjenesisInstantiator構(gòu)造器(底層就是使用了ObjenesisStd)return INSTANCE;}}
    【3】MockMethodInterceptor 攔截mock對象的所有方法

    前面說過攔截mock對象后執(zhí)行的是MockHandlerImpl的handle方法,那么是怎么調(diào)用到這個(gè)攔截方法的呢?
    先來看一下 bytebuddy 攔截過程,如下:
    就是通過method和intercept 分別制定攔截的方法和攔截后返回的結(jié)果

    Class<?> dynamicType = new ByteBuddy()// 指定父類.subclass(Object.class)// 根據(jù)名稱來匹配需要攔截的方法.method(ElementMatchers.named("toString"))// 攔截方法調(diào)用,返回固定值.intercept(FixedValue.value("Hello World!"))// 產(chǎn)生字節(jié)碼.make()// 加載類.load(getClass().getClassLoader())// 獲得Class對象.getLoaded();Assert.assertEquals("Hello World!", dynamicType.newInstance().toString());

    那再去看下Mockito是怎么通過ByteBuddy構(gòu)建的代理類

    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(),表示會攔截任一方法,攔截后會調(diào)用DispatcherDefaultingToRealMethod類里的方法.method(matcher).intercept(dispatcher).transform(withModifiers(SynchronizationState.PLAIN)).attribute(features.stripAnnotations? MethodAttributeAppender.NoOp.INSTANCE: INCLUDING_RECEIVER)//設(shè)置isHashCode和isEquals方法攔截后執(zhí)行的方法,分別是MockMethodInterceptor的ForHashCode和ForEquals方法.method(isHashCode()).intercept(hashCode).method(isEquals()).intercept(equals).serialVersionUid(42L)//設(shè)置一個(gè)MockMethodInterceptor類型的字段,并讓其實(shí)現(xiàn)MockAccess接口,這也是前面可以將mock對象轉(zhuǎn)成MockAccess對象的原因.defineField("mockitoInterceptor", MockMethodInterceptor.class, PRIVATE).implement(MockAccess.class).intercept(FieldAccessor.ofBeanProperty());

    通過源碼可以看出來,這里攔截mock對象的方法調(diào)用后,會將其交給mockitoInterceptor處理,調(diào)用其doIntercept方法,最終調(diào)用了其handler的handle方法,也就是一開始說的MockHandlerImpl的handle方法

    public static class DispatcherDefaultingToRealMethod {//我們通常調(diào)用mock方法攔截后進(jìn)入的應(yīng)該是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")@RuntimeTypepublic 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);}}

    總結(jié)

    以上是生活随笔為你收集整理的单元测试源码分析之一创建mock对象的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。