摘要:看到的函數(shù)返回類型,估計就是在這里實現(xiàn)了字節(jié)碼的轉(zhuǎn)換,然后返回了新的被掉包的文件了。雖然是門靜態(tài)類型語言,不過幸虧有字節(jié)碼和作為中間層,使得實現(xiàn)起來相對容易。
最近在工作中寫單元測試的時候,有使用到j(luò)mockit來mock無關(guān)對象。
在jmockit中,你可以使用MockUp來創(chuàng)建一個“fake”的實例,對某個方法指定自己的實現(xiàn),而不是調(diào)用實際的方法。
對于接口類型,需要這樣調(diào)用:
@Mocked private SomeInterface mockInstance; mockInstance = new MockUp() { ... }.getMockInstance();
這個倒沒有什么古怪的。估計又是使用了java.reflect.Proxy。這個技巧在很多Java框架中用到,比如Spring AOP對于接口類型的實現(xiàn),就是通過Proxy來混入攔截器實現(xiàn)的。
但是,對于其他類型的調(diào)用,就比較奇怪了:
@Mocked private SomeProxy mockInstance; new MockUp() { @Mock public int doSth() { return 1; } }; mockInstance.doSth(); // return 1
new出來的對象,如果沒有賦值給新的變量,應(yīng)該是隨著GC風飄云散了。可就是在我的眼皮底下,mockInstance就這樣被掉包了。
Spring AOP中,對于非接口類型,是通過CGLIB魔改字節(jié)碼來實現(xiàn)攔截器注入的。所以我估計這個也是一樣的道理。不過令人想不通的是,jmockit到底是什么時候進行移花接木的?沒看到注入的地方啊……
只能通過看代碼來揭秘了。先從MockUp的構(gòu)造函數(shù)開始吧。
// MockUp.java @Nonnull private ClassredefineClassOrImplementInterface(@Nonnull Class classToMock) { if (classToMock.isInterface()) { return createInstanceOfMockedImplementationClass(classToMock, mockedType); } Class realClass = classToMock; if (isAbstract(classToMock.getModifiers())) { classToMock = new ConcreteSubclass (classToMock).generateClass(); } classesToRestore = redefineMethods(realClass, classToMock, mockedType); return classToMock; }
對于非接口類型,調(diào)用了redefineMethods來定義一個仿版。順著redefineMethods找下去,終于發(fā)現(xiàn)了jmockit的“作案手法”。
// MockClassSetup.java @Nullable private byte[] modifyRealClass(@Nonnull Class> classToModify) { if (rcReader == null) { rcReader = createClassReaderForRealClass(classToModify); } MockupsModifier modifier = new MockupsModifier(rcReader, classToModify, mockUp, mockMethods); rcReader.accept(modifier, SKIP_FRAMES); return modifier.wasModified() ? modifier.toByteArray() : null; }
看到byte[]的函數(shù)返回類型,估計就是在這里實現(xiàn)了字節(jié)碼的轉(zhuǎn)換,然后返回了新的被掉包的class文件了。沿著MockupsModifier看下去,可以看到j(luò)mockit是用ASM來改動原來的實現(xiàn)(具體見external.asm這個包,我就沒有細看了)。
眾所周知,Java代碼先是編譯成class文件,然后由JVM加載運行的。圍繞JVM這一中間層,各種有趣的技術(shù)應(yīng)運而生。比如各種類加載器,可以動態(tài)地去加載同名的類的不同實現(xiàn)(不同的class文件)。還有各種魔改class文件的手段,在原來的實現(xiàn)中注入自己的代碼,像ASM、javassist、GCLIB,等等。jmockit就是應(yīng)用ASM來修改原來的class文件,用mocked的實現(xiàn)掉包原來的代碼。因為MockUp的構(gòu)造已經(jīng)觸發(fā)了“貍貓換太子”的幕后行為,所以這里就不用把new出來的東西賦值給具體變量了。
還有一個問題。我們雖然弄明白了jmockit的作案手法,可是還沒有找到掉包現(xiàn)場呢!即使現(xiàn)在jmockit已經(jīng)持有了被篡改后的字節(jié)碼,可它又是怎么替換呢?
繼續(xù)看下去,發(fā)現(xiàn)jmockit把修改后的字節(jié)碼存在StartUp.java里面了。轉(zhuǎn)過去會看到,jmockit這里用到了JDK6的一個新特性:動態(tài)Instrumentation。怪不得jmockit要求JDK版本知識在6以上。
關(guān)于動態(tài)Instrumentation,具體可以看下這篇文章:http://www.ibm.com/developerworks/cn/java/j-lo-jse61/
簡單來說,通過這一機制可以實現(xiàn)監(jiān)聽JVM加載類的事件,并在此之前運行自己的掛鉤方法。這么一來,掉包現(xiàn)場也找到了。
那jmockit怎么知道要監(jiān)聽哪些類呢?前面可以看到,需要Mock的類上,要添加Mocked注解。所以jmockit編寫了一些跟主流測試框架集成的代碼,在測試運行的時候獲取帶該注解的類。這樣就知道要監(jiān)聽的目標了。
總結(jié)一下:jmockit先通過Mocked注解標記需要Mock掉的類。然后調(diào)用new MockUp去創(chuàng)建修改后的class文件。在JVM運行的時候,通過JDK6之后的動態(tài)Instrumentation特性監(jiān)聽類加載事件,并在目標類加載之前移花接木,用魔改后的字節(jié)碼換掉真貨。雖然Java是門靜態(tài)類型語言,不過幸虧有字節(jié)碼和JVM作為中間層,使得mock實現(xiàn)起來相對容易。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/64488.html
摘要:單元測試中是否要靜態(tài)方法,一直爭論不休,網(wǎng)上有一個一個又一個的討論,各種意見都有。真要用來靜態(tài)方法,一般都是結(jié)合使用。等工具不支持靜態(tài)方法,原理上是因為它們都是基于的,只能通過創(chuàng)建子類或?qū)崿F(xiàn)接口的方式去。什么靜態(tài)方法構(gòu)造函數(shù),隨時隨地想就。 王者 Mockito 不知從何時開始,Mockito 成了 Java 的單元測試框架王者,目前(2019年7月)Github 上 star 數(shù)直逼...
摘要:單元測試框架作為的標準庫,是其他單元測試框架的基礎(chǔ)。可以和和配合使用編寫單元測試。官網(wǎng)地址單元測試覆蓋率工具單元測試中還需要用到代碼覆蓋率工具。代碼覆蓋率統(tǒng)計工具用來發(fā)現(xiàn)沒有被測試覆蓋的代碼,完善單元測試的覆蓋率。 在應(yīng)用程序中,單元是具有一個或多個輸入和單個輸出的軟件中最小可測試部分。單元...
摘要:引言前端開發(fā)經(jīng)常需要等待后端的接口,嚴重影響了開發(fā)效率,我們一般采用方式來避免這個問題。可能會涉及到門技術(shù),分別是服務(wù)端技術(shù)隨機生成特定格式數(shù)據(jù)的技術(shù)請求轉(zhuǎn)發(fā)請求攔截。 引言 前端開發(fā)經(jīng)常需要等待后端的接口,嚴重影響了開發(fā)效率,我們一般采用mock方式來避免這個問題。本人參考了大量文章,結(jié)合自己的經(jīng)驗,給出自己在mock上的一些理解。 1. 原理 何為mock,我認為mock主要就是通...
閱讀 3118·2021-11-10 11:36
閱讀 3318·2021-10-13 09:40
閱讀 6117·2021-09-26 09:46
閱讀 667·2019-08-30 15:55
閱讀 1414·2019-08-30 15:53
閱讀 1585·2019-08-29 13:55
閱讀 3002·2019-08-29 12:46
閱讀 3216·2019-08-29 12:34