单元测试--Mockito之spy

目录

 

spy和mock的相同点和区别:

Shadow和spy的异同点:

 使用场景一:

使用场景二:

使用场景三:

使用场景四:

典型错误示例:

spy对象和被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();//错误
   }
}

spy对象和被spy对象的关系

Object obj = new Object();

Object spyObj = spy(obj);

关于obj和spyObj的关系,下面我先直接给出结论:

1.可以说obj和spyObj类似是浅克隆的关系,而不是动态代理的关系。也就是在执行完Object spyObj = spy(obj);后,obj和spyObj的成员变量是指向同一个对象。

2.spyObj的类型中的方法和obj的类型的方法的不同之处在于spyObj的类型多了记录调用次数等一些跟统计有关的功能代码,当然还会判断某些标志位以确定是否调用实际代码(如:调用Mockito.doNothing().when(spyObj).equal(anyStrinig())后,执行spyObj.equals("")将不会执行Object#equals()方法)。

知道了以上关系后,那么在对spyObj打桩时就要注意了,不要通过obj 打桩,而要通过调用spyObj 打桩,否则这个桩就无效了。

以下是证明上述关系的代码和打印结果:

static class DataItem {
        ArrayList mData = new ArrayList(4);
        void setData(ArrayList list){
            mData = list;
        }

        void add(Object obj){
            mData.add(obj);
        }
    }
    @Test
    public void test(){
        DataItem dataSet = new DataItem();
        ArrayList list = new ArrayList<>(4);
        list.add(0);
        dataSet.setData(list);
        DataItem spySet = spy(dataSet);
        System.out.println(spySet.getClass().getName());
        //打印结果是  com.champion.loaddexdemo.ExampleUnitTest$DataItem$MockitoMock$988576336
        //结果说明spySet的类型是DataItem的子类
        System.out.println("dataSet length: " + dataSet.mData.size() + " spySet length: "
                + spySet.mData.size());
        //打印结果是  dataSet length: 1 spySet length: 1
        //结果说明spySet在创建对象是复制了dataSet的成员变量的
        dataSet.add(-1);
        System.out.println("dataSet length: " + dataSet.mData.size() + " spySet length: "
                + spySet.mData.size());
        //打印结果是  dataSet length: 2 spySet length: 2
        //结果说明spySet对dataSet的成员变量指向同一个对象,就是说是浅复制
        ArrayList spylist = new ArrayList<>(4);
        spylist.add(1);
        spylist.add(2);
        spylist.add(3);
        spySet.mData = spylist;
        System.out.println("dataSet length: " + dataSet.mData.size() + " spySet length: "
                + spySet.mData.size());
        //打印结果是  dataSet length: 2 spySet length: 3
        //结果说明spySet和dataSet是两个对象,有各自的成员变量
        spySet.add(4);
        System.out.println("dataSet length: " + dataSet.mData.size() + " spySet length: "
                + spySet.mData.size());
        //打印结果是  dataSet length: 2 spySet length: 4
        //结果说明调用spySet的方法时,并没有去调用dataSet的方法,说明spySet和datSet不是动态代理的关系
    }

 

  • 12
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值