单元测试源码分析之一创建mock对象
之前已經(jīng)介紹過(guò)Mockito和PowerMock的常見(jiàn)用法,PowerMock其實(shí)就是在Mockito的基礎(chǔ)上使用了字節(jié)碼技術(shù)使得其可以對(duì)靜態(tài)方法,私有方法等進(jìn)行插樁。
現(xiàn)在就先來(lái)看看Mockito是怎么創(chuàng)建一個(gè)mock對(duì)象的(本文的源碼都是來(lái)自mockito-core-3.2.0,低版本的底層實(shí)現(xiàn)不是ByteBuddy)
先看下一個(gè)比較簡(jiǎn)單的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);}}對(duì)于一個(gè)最基本的mock單測(cè),一般包含如下幾個(gè)部分:
1.構(gòu)建被mock的對(duì)象 userDao
2.構(gòu)建使用mock對(duì)象的實(shí)際對(duì)象 userService
3.建立mock對(duì)象和實(shí)際對(duì)象的關(guān)系
4.對(duì)mock對(duì)象進(jìn)行插樁
明顯可以看出這里的入口方法是 MockitoAnnotations.initMocks(this); ,那么看看這個(gè)方法是怎么幫我們分別構(gòu)建mock對(duì)象和實(shí)際對(duì)象并將其建立聯(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è)引擎分別對(duì)mock對(duì)象和spy對(duì)象進(jìn)行實(shí)例化,然后對(duì)添加了InjectMocks注解的對(duì)象進(jìn)行注入和實(shí)例化
看下IndependentAnnotationEngine的process方法:
通過(guò)反射拿到當(dāng)前clazz的所有field, 判斷其上是否有@Mock注解,如果有則通過(guò)this.createMockFor(annotation,field)方法創(chuàng)建mock的代理對(duì)象
最終通過(guò)如下代碼創(chuàng)建Mock對(duì)象, 可以看到 Mockito.mock(type, mockSettings);方法,就是早期沒(méi)有注解的時(shí)候用于創(chuàng)建mock對(duì)象的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對(duì)象的核心方法return Mockito.mock(type, mockSettings);}創(chuàng)建Mock對(duì)象
核心方法如下:
MockitoCore
[1] 真正創(chuàng)建mock對(duì)象的核心方法,主要做三件事情:
MockHandler
這里使用的是裝飾器模式,先創(chuàng)建一個(gè)MockHandlerImpl的handler對(duì)象,然后先后用NullResultGuardian,InvocationNotifierHandler包裝起來(lái)
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)聽(tīng)者,進(jìn)行各種方法回調(diào)return new InvocationNotifierHandler<T>(nullResultGuardian, settings);}其作用是給每個(gè)MockCreationSettings創(chuàng)建其mockHandler,然后在執(zhí)行mock/spy對(duì)象的方法時(shí),都會(huì)先被handler攔截,如果有進(jìn)行插樁,則執(zhí)行返回插樁的結(jié)果,如果沒(méi)有,則返回默認(rèn)值或者真實(shí)執(zhí)行結(jié)果
MockHandlerImpl
先簡(jiǎn)單分析到這里,后面再來(lái)看這部分源碼
public Object handle(Invocation invocation) throws Throwable {...// 根據(jù)invoation查找對(duì)應(yīng)stubbingStubbedInvocationMatcher stubbing = invocationContainer.findAnswerFor(invocation);if (stubbing != null) {//有插樁則返回插樁結(jié)果stubbing.captureArgumentsFrom(invocation);try {return stubbing.answer(invocation);} finally {mockingProgress().reportOngoingStubbing(ongoingStubbing);}} else {//沒(méi)有則使用DefaultAnswer應(yīng)答并返回結(jié)果值Object ret = mockSettings.getDefaultAnswer().answer(invocation); invocationContainer.resetInvocationForPotentialStubbing(invocationMatcher);return ret;}}createMock
先來(lái)看下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對(duì)象的loadPlugin方法,看名字應(yīng)該是根據(jù)class加載插件的,我們看下其實(shí)現(xiàn)
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對(duì)象)
ByteBuddy是java字節(jié)碼增強(qiáng)框架,可以動(dò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方法對(duì)resources進(jìn)行了過(guò)濾String classOrAlias = new PluginFinder(pluginSwitch).findPluginClass(Iterables.toIterable(resources));//找到對(duì)應(yīng)的PluginClass之后通過(guò)反射實(shí)例化if (classOrAlias != null) {//如果找到的class名稱和指定的別名一樣,就通過(guò)別名去plugins里預(yù)先加載的插件信息里獲取class名稱(個(gè)人感覺(jué)這里設(shè)計(jì)的太復(fù)雜了,為了獲得一個(gè)class名稱)if (classOrAlias.equals(alias)) {classOrAlias = plugins.getDefaultPluginClass(alias);}Class<?> pluginClass = loader.loadClass(classOrAlias);//通過(guò)反射實(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è)方法,通過(guò)前面的分析知道這里的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 )
[1] createMockType
public SubclassByteBuddyMockMaker(SubclassLoader loader) {//注意這里傳入的BytecodeGenerator的實(shí)例類型是SubclassBytecodeGenerator,因?yàn)槭褂玫氖侵粠б粋€(gè)參數(shù)的構(gòu)造方法,matcher參數(shù)是any(),后面會(huì)使用到cachingMockBytecodeGenerator = new TypeCachingBytecodeGenerator(new SubclassBytecodeGenerator(loader), false);}@Overridepublic <T> Class<? extends T> createMockType(MockCreationSettings<T> settings) {try {//根據(jù)settings構(gòu)建MockFeatures,再通過(guò)TypeCachingBytecodeGenerator創(chuàng)建mock對(duì)象的代理類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的代理類,如果沒(méi)有就創(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方法 一開始肯定是沒(méi)有緩存的,所以調(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之前會(huì)先調(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í)例對(duì)象,也就是說(shuō)
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 {//沒(méi)有就使用默認(rèn)的ObjenesisInstantiator構(gòu)造器(底層就是使用了ObjenesisStd)return INSTANCE;}}【3】MockMethodInterceptor 攔截mock對(duì)象的所有方法
前面說(shuō)過(guò)攔截mock對(duì)象后執(zhí)行的是MockHandlerImpl的handle方法,那么是怎么調(diào)用到這個(gè)攔截方法的呢?
先來(lái)看一下 bytebuddy 攔截過(guò)程,如下:
就是通過(guò)method和intercept 分別制定攔截的方法和攔截后返回的結(jié)果
那再去看下Mockito是怎么通過(guò)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(),表示會(huì)攔截任一方法,攔截后會(huì)調(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對(duì)象轉(zhuǎn)成MockAccess對(duì)象的原因.defineField("mockitoInterceptor", MockMethodInterceptor.class, PRIVATE).implement(MockAccess.class).intercept(FieldAccessor.ofBeanProperty());通過(guò)源碼可以看出來(lái),這里攔截mock對(duì)象的方法調(diào)用后,會(huì)將其交給mockitoInterceptor處理,調(diào)用其doIntercept方法,最終調(diào)用了其handler的handle方法,也就是一開始說(shuō)的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对象的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Notepad++ 去除CRLF符号
- 下一篇: 单元测试源码分析之二Mockito自动装