單元測試--Mockito之spy

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();//錯誤
   }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章