A. c#.當構造函數為私有時,該類的用法。
如果當前類的全部構造函數均睜兆虛為私有時,你可猜轎以用以下方法來使用這個類:
1、給當前類中,聲明一個靜態屬性或方法,用於返回當前類的實例來使用;
2、將當前類中的所有需要使用到的成員都設置成為static,這樣可以直接使用[類名.成員名]來使用;
最經典的設置構造方法為私有的用法悉燃是使用單例模式。
public class Manager
{
private Manager(){}
private static Manager _c;
public static Manager CurrentInstance{ get { _c = _c ?? new Manager(); } }
}
B. 怎樣測試method中變數的值 mock
怎樣測試method中變數的值 mock
mock測試就是在測試過程中,對於某些不容易構造或者不容易獲取的對象,用一個虛擬的對象來創建以便測試的測試方法。
C. 使用Powermock對私有方法進行mock
團譽宏 在public方法中往往會調用一些private方法,如果private方法很復雜,我們就需要處理很多方法的mock。如果這時只想要測試public方法,並不想關注private方法的邏輯,那麼就需要虛歷對private方法進行mock。下面我們簡單介紹下如何通過Powermock來對私有方法進行mock。
被測試類:
此類中包含一個public方法mockPrivateFunc,裡面調用了private方法privateFunc。當前我們想要測試該public方法,並且不想進入private方法執行,那麼就需要對該塌冊私有方法進行模擬。
測試類:
1、由於是對本類的私有方法進行模擬,所以需要在PrepareForTest後面加上MockPrivateClass,同時需要使用spy來模擬一個對象。
2、 使用下面方式來模擬私有方法:
如果私有方法存在參數,則同樣的需要在私有方法名後面帶上同樣個數及類型的參數,其方法原型為:
3、使用verifyPrivate來驗證私有方法被調用,其例如下:
verifyPrivate的參數為mock的對象,及驗證模式(可選),invoke參數為要驗證的私有方法名稱及相應參數。
D. 如何對Java單例模式進行mock
JAVA單例模式的幾種實現方法餓漢式單例類packagepattern.singleton;//餓漢式單例類.在類初始化時,已經自行實例化publicclassSingleton1{//私有的默認構造子privateSingleton1(){}//已經自行實例化=newSingleton1();//靜態工廠方法(){returnsingle;}}2.懶漢式單例類packagepattern.singleton;//懶漢式單例類.在第一次調用的時候實例化publicclassSingleton2{//私有的默認構造子privateSingleton2(){}//注意,這里沒有;//只實例化一次static{single=newSingleton2();}//靜態工廠方法(){if(single==null){single=newSingleton2();}returnsingle;}}在上面給出懶漢式單例類實現里對靜態工廠方法使用了同步化,以處理多線程環境。有些設計師在這里建議使用所謂的"雙重檢查成例".必須指出的是,"雙重檢查成例"不可以在Java語言中使用。不十分熟悉的讀者,可以看看後面給出的小節。同樣,由於構造子是私有的,因此,此類不能被繼承。餓漢式單例類在自己被載入時就將自己實例化。即便載入器是靜態的,在餓漢式單例類被載入時仍會將自物純啟己實例化。單從資源利用效率角度來講,這個比懶漢式單例類稍差些。從速度和反應時間角度來講,則比懶褲悶漢式單例類稍好些。然而,懶漢式單例類在實例化時,必須處理好在多個線程同時首次引用此類時的訪問限制問題,特別是當單例類作為資源控制器,在實例化時必然涉及資源初始化,而資源初始化很有可能耗費時間。這意味著出現多線程同時首次引用此類的機率罩如變得較大。餓漢式單例類可以在Java語言內實現,但不易在C++內實現,因為靜態初始化在C++里沒有固定的順序,因而靜態的m_instance變數的初始化與類的載入順序沒有保證,可能會出問題。這就是為什麼GoF在提出單例類的概念時,舉的例子是懶漢式的。他們的書影響之大,以致Java語言中單例類的例子也大多是懶漢式的。實際上,本書認為餓漢式單例類更符合Java語言本身的特點。3.登記式單例類.packagepattern.singleton;importjava.util.HashMap;importjava.util.Map;//登記式單例類.//類似Spring裡面的方法,將類名注冊,下次從裡面直接獲取。publicclassSingleton3{privatestaticMapmap=newHashMap();static{Singleton3single=newSingleton3();map.put(single.getClass().getName(),single);}//保護的默認構造子protectedSingleton3(){}//靜態工廠方法,返還此類惟一的實例(Stringname){if(name==null){name=Singleton3.class.getName();System.out.println("name==null"+"--->name="+name);}if(map.get(name)==null){try{map.put(name,(Singleton3)Class.forName(name).newInstance());}catch(InstantiationExceptione){e.printStackTrace();}catch(IllegalAccessExceptione){e.printStackTrace();}catch(ClassNotFoundExceptione){e.printStackTrace();}}returnmap.get(name);}//一個示意性的商業方法publicStringabout(){return"Hello,IamRegSingleton.";}publicstaticvoidmain(String[]args){Singleton3single3=Singleton3.getInstance(null);System.out.println(single3.about());}}
E. 怎麼用mockito mock 單例
當被問到要實現一個單例模式時,很多人的第一反應是寫出如下的代碼,包括教科書上也是這樣教我們的。一二三四5陸漆吧9一0一一publicclassSingleton{;privateSingleton(){}(){if(instance==null){instance=newSingleton();}returninstance;}}這段代碼簡單明了,而且使用了懶載入模式,但是卻存在致命的問題。當有多個線程並行調用getInstance()的時候,就會創建多個實例。也就是說在多線程下不能正常工作。懶漢式,線程安全為了解決上面的問題,最簡單的方法是將整個getInstance()方法設為同步(synchronized)。一二三四5陸(){if(instance==null){instance=newSingleton();}returninstance;}雖然做到了線程安全,並且解決了多實例的問題,但是它並不高效。因為在任何時候只能有一個線程調用getInstance()方法。但是同步操作只需要在第一次調用時才被需要,即第一次創建單例實例對象時。這就引出了雙重檢驗鎖。雙重檢驗鎖雙重檢驗鎖模式(doublecheckedlockingpattern),是一種使用同步塊加鎖的方法。程序員稱其為雙重檢查鎖,因為會有兩次檢查instance==null,一次是在同步塊外,一次是在同步塊內。為什麼在同步塊內還要再檢驗一次?因為可能會有多個線程一起進入同步塊外的if,如果在同步塊內不進行二次檢驗的話就會生成多個實例了。一二三四5陸漆吧9一(){if(instance==null){//SingleCheckedsynchronized(Singleton.class){if(instance==null){//DoubleCheckedinstance=newSingleton();}}}returninstance;}這段代碼看起來很完美,很可惜,它是有問題。主要在於instance=newSingleton()這句,這並非是一個原子操作,事實上在JVM中這句話大概做了下面三件事情。給instance分配內存調用Singleton的構造函數來初始化成員變數將instance對象指向分配的內存空間(執行完這步instance就為非null了)但是在JVM的即時編譯器中存在指令重排序的優化。也就是說上面的第二步和第三步的順序是不能保證的,最終的執行順序可能是一-二-三也可能是一-三-二。如果是後者,則在三執行完畢、二未執行之前,被線程二搶佔了,這時instance已經是非null了(但卻沒有初始化),所以線程二會直接返回instance,然後使用,然後順理成章地報錯。我們只需要將instance變數聲明成volatile就可以了。一二三四5陸漆吧9一0一一一二一三一四一5一陸publicclassSingleton{;//聲明成volatileprivateSingleton(){}(){if(instance==null){synchronized(Singleton.class){if(instance==null){instance=newSingleton();}}}returninstance;}}有些人認為使用volatile的原因是可見性,也就是可以保證線程在本地不會存有instance的副本,每次都是去主內存中讀取。但其實是不對的。使用volatile的主要原因是其另一個特性:禁止指令重排序優化。也就是說,在volatile變數的賦值操作後面會有一個內存屏障(生成的匯編代碼上),讀操作不會被重排序到內存屏障之前。比如上面的例子,取操作必須在執行完一-二-三之後或者一-三-二之後,不存在執行到一-三然後取到值的情況。從「先行發生原則」的角度理解的話,就是對於一個volatile變數的寫操作都先行發生於後面對這個變數的讀操作(這里的「後面」是時間上的先後順序)。但是特別注意在Java5以前的版本使用了volatile的雙檢鎖還是有問題的。其原因是Java5以前的JMM(Java內存模型)是存在缺陷的,即時將變數聲明成volatile也不能完全避免重排序,主要是volatile變數前後的代碼仍然存在重排序問題。這個volatile屏蔽重排序的問題在Java5中才得以修復,所以在這之後才可以放心使用volatile。相信你不會喜歡這種復雜又隱含問題的方式,當然我們有更好的實現線程安全的單例模式的法。餓漢式staticfinalfield這種方法非常簡單,因為單例的實例被聲明成static和final變數了,在第一次載入類到內存中時就會初始化,所以創建實例本身是線程安全的。一二三四5陸漆吧9一0publicclassSingleton{//類載入時就初始化=newSingleton();privateSingleton(){}(){returninstance;}}這種寫法如果完美的話,就沒必要在啰嗦那麼多雙檢鎖的問題了。缺點是它不是一種懶載入模式(lazyinitialization),單例會在載入類後一開始就被初始化,即使客戶端沒有調用getInstance()方法。餓漢式的創建方式在一些場景中將無法使用:譬如Singleton實例的創建是依賴參數或者配置文件的,在getInstance()之前必須調用某個方法設置參數給它,那樣這種單例寫法就無法使用了。靜態內部類staticnestedclass我比較傾向於使用靜態內部類的方法,這種方法也是《EffectiveJava》上所推薦的。一二三四5陸漆吧9publicclassSingleton{{=newSingleton();}privateSingleton(){}(){returnSingletonHolder.INSTANCE;}}這種寫法仍然使用JVM本身機制保證了線程安全問題;由於SingletonHolder是私有的,除了getInstance()之外沒有法訪問它,因此它是懶漢式的;同時讀取實例的時候不會進行同步,沒有性能缺陷;也不依賴JDK版本。枚舉Enum用枚舉寫單例實在太簡單了!這也是它最大的優點。下面這段代碼就是聲明枚舉實例的通常做法。一二三publicenumEasySingleton{INSTANCE;}我們可以通過EasySingleton.INSTANCE來訪問實例,這比調用getInstance()方法簡單多了。創建枚舉默認就是線程安全的,所以不需要擔心doublecheckedlocking,而且還能防止反序列化導致重新創建新的對象。但是還是很少看到有人這樣寫,可能是因為不太熟悉吧。總結一般來說,單例模式有五種寫法:懶漢、餓漢、雙重檢驗鎖、靜態內部類、枚舉。上述所說都是線程安全的實現,文章開頭給出的第一種方法不算正確的寫
F. mock單元測試 mockito實踐
@Rule
public MokitoRule rule = MockitoJUnit.rule();
注意這里的修飾符public,如果沒有這個修飾符的話使用mock測試會報錯
@Rule
public ExpectedException thrown = ExpectedException.none();
可以選擇忽視拋悶肆出的異常?(我是這么理解的不知道是否正確)
這里我還沒有看juint執行的邏輯,只是看了mock環境下獲取註解創建mock對象,並將mock的對向注入到@Injectmocks的目標測試對象中去的邏輯。
使用的jar包版本是junit.junit.4.11,org.powermock.powermock-api-mockito.1.6.6
創建一個interface用於目標測試類的filed的type。
創建一個目標測試類,其中聲明filed的type為上面的interface的type
最後創建一個測試用例對象用於測試目標測試類
我從rule對象執行的時候開始追蹤,junit的運行原理略過
其中的testClass就是測試用例,MyMockTest的實例,annotationEngine是默認的註解驅動InjectingAnnotationEngine,
這個方法的內部獲取測試用例的type,獲取註解驅動,並判斷是否是默認的註解驅動,可以自定義註解驅動?(暫時我還辦不到),之後註解驅動執行
InjectingAnnotationEngine.process 內部只有兩個方法,從名字和其上的注釋可以螞困轎知道
processIndependentAnnotations處理獨立的filed,其實就是測試用例中有@mock註解的filed,這里就是a和b。processInjectMocks處理依賴於獨立mock對象的filed,就是測試用例中有@InjectMocks註解的filed,依賴於mock對象的目標測試類,這里就是DoMainObject,先看processInjectMocks
入參分別為測試用例的type,和instance,方法中只有一個循環,在循環的內部處理三件事
1delegate.process(classContext, testInstance);委派對尺缺象處理@Mock,@Captor等註解,
2spyAnnotationEngine.process(classContext, testInstance);監視註解驅動處理@Spy註解
3獲取測試用例的父類,賦值給原來的變數
4如果Class的type為Object,跳出循環
這個方法就是先處理自己的獨立註解,然後去處理父類的獨立註解,如此往復直到父類為Object源類。
這個方法參數還是Class的type和Class 的instance,
處理過程是獲取instance的所有field就是所有的屬性,然後循環獲取filed的上的所有註解,更具註解和field嘗試創建mock對象,這里最後的創建對象時使用cblib創建代理對象,最後創建一個Setter對象將創建的cglib代理對象mock對象,set進instance的field中去,即完成了一個測試用例中的屬性的注入(spring的bean註解注入方式是不是也是如此呢,所有的基於註解的實現原理是否基本類似於此呢)
這里只關注兩個方法createMockFor和FieldSetter(testInstance, field).set(mock)
createMockFor方法的流程比較復雜,
這個方法的內部有兩個方法,
在這個方法中對annotationd的類型與已有的註解處理器對象集合進行判斷是否包含,如果包含取出對應的處理器對象,如果不包含空實現一個註解處理器實現process方法返回為null。也就是說在DefaultAnnotationEngine對象的實例中只處理特定的註解生成其mock代理對象。
這個註解處理器的集合是在4中創建了deletage時創建了DefaultAnnotationEngine對象,然後在其構造方法中調用了注冊註解驅動方法
private final Map, FieldAnnotationProcessor?> annotationProcessorMap = new HashMap, FieldAnnotationProcessor>();
根據獲取的annotationProcess對象執行process方法,如果不是從map中獲取的那麼返回值就是null,在本測試中就是@mock的方法返回了MockAnnotationProcessor類型的註解驅動,
Mockito.mock(field.getType(), mockSettings);這個就是最後創建mock的cglib代理對象的方法,對這個方法暫時就不繼續追蹤了,
現在我們已經將一個@Mock註解下的測試類中的field建立好了,讓我們回到5的process方法中,能看見這個步驟就是重復的執行這段邏輯:
獲取field的所有註解,調用createMockFor方法,然後在此方法中和DefaultAnnotationEngine預置的FieldAnnotationProcessor 實現類型集合做匹配,滿足的獲取指定的註解處理器創建對應的mock對象。不滿足的創建一個匿名子類,其中實現的方法指定返回null。以此將@Mock等註解和其他註解區分開來,只創建@Mock和@Captor等獨立的註解。如此步驟processIndependentAnnotations.process()就完成了。
spyAnnotationEngine.process的執行類似,但是這個註解處理類是使用反射去根據類型創建一個真實的實例對象返回而不是創建一個mock的cglib對象。
現在我們完成了processIndependentAnnotations,來看看現在的測試用例instance
可以看到現在a,b使用@Mock註解的field已經存在cglib的代理對象了,使用@InjectMocks的doMainObject暫時還是null,現在來看processInjectMocks
方法內部的核心方法是injectMocks,內部的邏輯從子類到父類最後到Object處理每個繼承層級的@InjectMocks註解
方法內處理
1獲取測試用例的Class類型
2創建一個Field對象的set集合
3循環處理
4將class中所有InjectMocks註解的field放到mockDependentFields集合中
5將創建的mock對象添加到mocks集合中
6Class類型是Object跳出循環
7創建@InjectMock註解修飾的field
這是根據依賴創建目標測試類的mock對象。
最後方法完成的時候
可以看到目標測試類創建完成,依賴a,b也已經注入。
沒有去追蹤Junit和cglib只是將中間mock的註解的過程進行了追蹤:
基本就是先創建mock對象,然後將根據依賴創建@InjectMocks的目標測試類對象
其中註解的區分
1@InjectMocks和其他類型不同,
2@Spy和@Mock,@Captor等註解不同
在使用的過程中Rule一定要是pulic修飾的
在使用mockito的時候還出現了mock的對象在測試的時候報空指針的問題,我追蹤後發現是同類型的interface在注入@InjectMocks修飾的主目標對象的時候是有排序的,會根據測試類中的filedName進行排序依次向下注入,解決辦法就是把@InjectMocks的所有field都進行mock。