spy和mock的相同點和區別:
1.得到的對象同樣可以進行“監管”,即驗證和打樁。
2.如果不對spy對象的methodA打樁,那麼調用spy對象的methodA時,會調用真實方法。
3.如果不對mock對象的methodA打樁,將doNothing,且返回默認值(null,0,false)。
Shadow和spy的異同點:
1.同樣可以對對象的特定public方法進行打樁。
2.Shadow可以對public static的方法進行打樁,spy不可以。
3.當測試對象爲Shadow對象時,無法被統計覆蓋率,spy對象可以被統計。
4.Shadow需要定義一個類,並且需要使用註解,spy不用。
5.無法對Shadow對象的public方法進行verify,spy對象可以。
6.Shadow對象不需要我們主動顯式注入到測試程序中,因爲測試程序在創建Shadow的目標對象後,會自動爲目標對象打樁,而spy對象需要我們主動顯式注入到測試程序中,否則樁無法生效,也無法verify。1.
使用場景一:
public class TestSubject{
public void methodA(){
throw new RuntimeException();
}
public void methodB(){
System.out.println("methodB begin");
methodA();
methodC()
System.out.println("methodB end");
}
public void methodC(){
System.out.println("methodC");
}
}
public class Test{
//此用例中使用spy的原因是我要測試的是TestSubject的methodB方法,所以調用methodB時必須執行其
//真實的方法體,methodB會調用methodA,methodA會拋異常,所以要繞過methodA
@Test
public void testMethodB(){
TestSubject t = new TestSubject();
TestSubject spyT = Mockito.spy(t);
//避免調用mehtodB時拋運行時異常。
doNothing().when(spyT).methodA();
sptT.methodB();
}
}
使用場景二:
public class TestSubject{
public int methodA(){
//根據某成員變量的值去計算得出一個value,這個過程包含了複雜的邏輯和層層方法嵌套調用
return value;
}
public void methodB(){
int key = methodA();
switch(key){
case 0:
//do something
case 1:
//do something
case 2:
//do something
}
}
}
public class Test{
//此用例中使用spy的原因是我要測試的是TestSubject的methodB方法,所以需要
//調用真實對象的methodB,methodB的輸入來自methodA的返回值。但是methodA的計算十分複雜,
//那麼想要methodA返回你想要的值就不那麼容易,別人看起來也不直觀,不確定methodA否是真的
//返回0,1,2。那麼就可以對methodA打樁,對真實對象打樁,就要用到spy.
@Test
public void testMethodB(){
TestSubject t = new TestSubject();
TestSubject spyT = Mockito.spy(t);
//第一次,第二次,第三次調用methodA時,分別返回0,1,2
when(spyT.methodA()).thenReturn(0,1,2);
for(int i=0; i<=2; i++){
spyT.methodB();
}
//assert && verify
}
}
使用場景三:
public class TestSubject{
public void methodA(){
System.out.println("methodA");
}
public void methodB(int i){
int key;
//根據參數i進行復雜運算,得出結果賦值給key
switch(key){
case 0:
methodA();
case 1:
methodC();
case 2:
methodD();
}
}
public void methodC(){
System.out.println("methodC");
}
public void methodD(){
System.out.println("methodD");
}
}
public class Test{
//此用例中使用spy的原因是我要測試的是TestSubject的methodB方法,所以需要
//調用真實對象的methodB,此例中需要verify輸入特定的i,是否能分別走進case 0,1,2,
//methodA,C,D方法體內的東西都沒法獲取並證明methodA,C,D被調用過。那麼就只能verify了,
//verify只能針對mock對象,其實spy對象,也可以使用verify
@Test
public void testMethodB(){
TestSubject t = new TestSubject();
TestSubject spyT = Mockito.spy(t);
//假定輸入1,能讓key==0
spyT.methodB(1);
//assert && verify
verify(spyT).methodA();
}
}
使用場景四:
public class TestSubject{
public void methodB(TestObject obj, i){
int key;
//這方法執行的內容非常必要,所以obj需要真實對象。
obj.doImportantThing();
//根據參數i進行復雜運算,得出結果賦值給key
switch(key){
case 0:
LayoutInflater inflater = obj.getLayoutInfalter();
ViewGroup v = inflater.inflate(R.layout.complex_layout,null,false);
v.setVisibility(View.GONE);
//do something can be verify
case 1:
methodC();
case 2:
methodD();
}
}
public void methodC(){
System.out.println("methodC");
}
public void methodD(){
System.out.println("methodD");
}
}
public class TestObject{
public void doImportantThing(){
//do something nessisary for TestSubject
}
//一個layout文件經常無法inflate出一個ViewGroup,所以很可能你需要該方法返回一個
//mock對象,然後你可以隨心所欲指定inflate出來的ViewGroup對象
public LayoutInflater getLayoutInfalter(){
//obtain LayoutInflater
}
}
public class Test{
//此用例中使用spy的原因是我要測試的是TestSubject的methodB方法,所以需要
//調用真實對象的methodB,此例中需要verify輸入1後,是否進入case 0;因爲
//TestObject#doImportantThing()中的內容是必須執行的,所以TestObject需要傳入的
//真實對象,但是R.layout.complex_layout太複雜了,裏面都是廠商定製的控件,無法加載,
//進入case 0後,將無法正常跑下去,那麼可以通過spy TestObject,然後對getLayoutInfalter
//打樁,使得返回一個mock LayoutInfalter,然後再對mock LayoutInfalter的inflate方法打樁,
//使得不去真正加載R.layout.complex_layout,而是返回一個自己創建好ViewGroup,使得代碼
//能繼續跑下去
@Test
public void testMethodB(){
TestSubject t = new TestSubject();
TestSubject spyT = Mockito.spy(t);
TestObject obj = new TestObject();
TestObject spyObj = Mockito.spy(obj);
LayoutInflater mockInflater = mock(LayoutInflater.class);
ViewGroup mockViewGroup = mock(ViewGroup.class);
when(mockInflater).inflate(anyInt(), isNull(ViewGroup.class), anyBoolean()).thenReturn(mockViewGroup);
doReturn(mockInflater).when(spyObj).getLayoutInfalter();
//假定輸入1,能讓key==0
spyT.methodB(1);
//assert && verify
verify(mockViewGroup).setVisibility(View.GONE);
//verify other
}
}
總而言之,如果你想對一個真實對象的某個方法打樁( doReturn().when().method() ),verify真實對象的public方法( verify().method() ),繞過真實對象的某個public方法( doNothing().when().method() ),你可以使用spy後的對象,如:
TestSubject t = new TestSubject();
TestSubject spyT = Mockito.spy(t);
特別需要注意的是,t和spyT是兩個不同的對象,後面的代碼必須要使用spyT,打樁纔有效,才能verify TestSubject的方法。如果你只是spy(t),而後面的代碼仍然調用t.methodB()的話,則打樁無效,無法verify。而要是保證調用的是spyT.methodB()。
典型錯誤示例:
public class TestSubject{
public void methodA(){
throw new RuntimeException();
}
public void methodB(){
System.out.println("methodB begin");
methodA();
methodC()
System.out.println("methodB end");
}
public void methodC(){
System.out.println("methodC");
}
public void mothodD(TestObject obj){
obj.doSomething();
}
}
public class TestObject{
public void doSomething(){
}
}
public class Test{
//錯誤用法1
@Test
public void testMethodB(){
TestSubject t = new TestSubject();
TestSubject spyT = Mockito.spy(t);
//避免調用mehtodB時拋運行時異常。
doNothing().when(spyT).methodA();
t.methodB();//錯誤
//spyT.methodB();//正確
}
//錯誤用法2
@Test
public void testMethodB(){
TestSubject t = new TestSubject();
TestSubject spyT = Mockito.spy(t);
//避免調用mehtodB時拋運行時異常。
doNothing().when(t).methodA();//錯誤
//doNothing().when(spyT).methodA();//正確
spyT.methodB();
}
//錯誤用法3
@Test
public void testMethodB(){
TestSubject t = new TestSubject();
TestObject obj = new TestObject();
TestObject spyObj = Mockito.spy(obj);
t.methodD(obj);//錯誤
//t.methodD(spyObj);//正確
//verify
verify(spyObj).doSomething();//正確
//verify(obj).doSomething();//錯誤
}
}