51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

查看: 472|回复: 0

[原创] 使用Mockito进行单元测试分享

[复制链接]

该用户从未签到

发表于 2022-12-12 15:49:14 | 显示全部楼层 |阅读模式
本帖最后由 草帽路飞UU 于 2022-12-12 15:50 编辑

Mockito简介以及工作流程



  Mockito是一个用于在软件测试中模拟对象的开源框架,使用Mockito很大程度简化了对具有外部依赖项的类的测试开发。

  mock的对象就是接口或者类的一个虚拟的实现,他允许自己定义方法的输出。通常是模拟比如和其他系统的交互信息然后再进行测试验证。


  mock的流程:



1. mock出来测试类的依赖,自定义输出的结果。


  2. 执行测试类代码。


  3. 验证执行的结果是否和预期一致。


  工程添加Mockito依赖


 项目使用的是maven构建,需要添加下面的依赖。我项目中使用的是Junit4。

  <dependency>


      <groupId>org.mockito</groupId>


      <artifactId>mockito-inline</artifactId>


      <version>3.3.3</version>


  </dependency>


  <dependency>


      <groupId>junit</groupId>


      <artifactId>junit</artifactId>


      <version>4.13.2</version>


      <scope>test</scope>


  </dependency>




  Mockito的使用


  如何mock出一个新的对象

Mockito提供一下几种方式来创建出mock的对象:

  ·在Junit5中,使用@ExtendWith(MockitoExtension.class)注解和@Mock注解结合使用来mock对象


  · 使用静态的mock()方法直接mock对象


  · 使用@Mock注解,这边注意如果要使用@Mock注解的话,需要触发一个初始化的操作,初始化方法是一个静态的方:MockitoAnnotations.initMocks(this)


  下面看一个简单的例子:


  新建两个类,Database和Service类,Database中提供两个方法,isAvailable和getUniqueId,Service类中依赖Database,提供一个query方法调用databse的isAvailable方法,都只是测试使用。



  /**

   * 提供两个方法


   *


   * @since 2022/1/4


   */


  public class Database {


      public boolean isAvailable() {


          return false;


      }


      public int getUniqueId() {


          return 22;


      }


  }


  /**


   * 依赖database


   *


   * @since 2022/1/4


   */


  public class Service {


      private final Database database;


      public Service(Database database) {


          this.database = database;


      }


      public boolean query(String query) {


          return database.isAvailable();


      }


      @Override


      public String toString() {


          return "Using database with id: " + database.getUniqueId();


      }


  }




  使用Mockito来模拟Database对象的单元测试可以按照下面的方式编写:


  /**

   * @since 2022/1/4


   */


  public class ServiceTest {


      @Mock


      Database database; // 2


      @InjectMocks


      Service service; // 3


      @Before


      public void before() {


          MockitoAnnotations.initMocks(this); // 1


      }


      @Test


      public void testQuery() {


          Assert.assertNotNull(database);


          Mockito.when(database.isAvailable()).thenReturn(true); // 4


          final boolean query = service.query("select * from database"); // 5


          Assert.assertTrue(query);


      }


  }


1. 第一步先触发一个初始化的操作,初始化方法是一个静态的方:MockitoAnnotations.initMocks(this),写在了Before方法下面。


  2. 在第一步的基础之上,可以使用@Mock注解mock出来一个database对象。


  3. @InjectMocks注解表示可以通过构造函数、setter方法或者属性注入的方式注入mock的对象,这边service对象提供了一个包含database的构造函数,就可以通过这个注解把mock的database注入进


来。

  4. 配置mock的对象,设置当isAvailable方法被调用的时候返回true,这些后面会再说明一下。


  5. 执行测试的方法,然后用断言判断方法的返回值是否正确。


  在mock的对象上面配置方法调用的返回值


  上面讲过,Mockito可以自己配置在mock对象上调用方法的返回值。同时,对于没有配置返回值的方法,会返回空值:


  ·对于Object类型返回null


  · 数字类型返回0


  · boolean类型返回false


  …


  下面说明一下几个常用的用法:


  1. when().thenReturn() 和 when().thenThrow()





使用when(…).thenReturn(…)可以在方法调用的时候,使用预置的参数来返回想要指定的值。

  也可以使用比如anyString或者anyInt这些方法来定义输入的参数,这边就是对于所有的输出都返回指定的值,注意,在mock的时候要么全部使用anyString这种模拟的参数,要么就全部使用真实的参


数,不然mock会失败。

  如果想定义多个返回的值,thenReturn也可以支持链式的调用,他会按照指定的顺序一个一个的返回。


  具体案例:


  /**


   * @since 2022/1/21


   */


  public class MockitoWhenTest {


      @Mock


      List<String> mockList;


      @Mock


      Comparator<Integer> comparator;


      @Before


      public void setUp() {


          MockitoAnnotations.initMocks(this);


      }


      // 测试返回指定的值 指定参数


      @Test


      public void testReturnConfiguredValue() {


          Mockito.when(mockList.get(0)).thenReturn("Hello");


          Assert.assertEquals(mockList.get(0), "Hello");


      }


      // mock可以指定多个返回值 会按照顺序返回


      @Test


      public void testMoreThanOneReturnValue() {


          Mockito.when(mockList.get(0)).thenReturn("Hello").thenReturn("World");


          Assert.assertEquals(mockList.get(0), "Hello");


          Assert.assertEquals(mockList.get(0), "World");


      }


      // 可以指定anyInt anyString等类型 不限定参数的输入 都返回配置的值


      @Test


      public void testReturnValueUseAnyParameter() {


          // 测试用 不要在意功能


          Mockito.when(comparator.compare(9999,9999)).thenReturn(100);


          Assert.assertEquals(comparator.compare(9999,9999), 100);


          Mockito.when(comparator.compare(anyInt(), anyInt())).thenReturn(222);


          Assert.assertEquals(comparator.compare(9,232), 222);


      }


  }


  使用when().thenReturn()可以用来抛出异常,使用方法很简单,具体案例:


  /**


   * @since 2022/1/21


   */


  public class MockitoThrowTest {


      @Test


      public void testThrow() {


          // 这边使用静态的mock方法模拟对象


          Demo demo = Mockito.mock(Demo.class);


          Mockito.when(demo.getAge(anyInt())).thenThrow(new IllegalArgumentException("Warning"));


          IllegalArgumentException exception = Assert.assertThrows(IllegalArgumentException.class, () -> demo.getAge(33));


          Assert.assertEquals(exception.getMessage(), "Warning");


      }



      static class Demo {


          public int getAge(int age) {


              return age;


          }


      }


  }




  2. doReturn().when() 和doThrow().when()


  这两个方法和when().thenReturn()、when().thenThrow()方法一样,都可以模拟mock对象方法的返回,后面这种应该好理解一点。但是对使用@Spy注解模拟的对象来说,doReturn这种方法是有用


的。

  对于真实的对象,可以通过@Spy注解来模拟。而且真实的对象的话,会调用真实的方法,如果继续用when().thenReturn()的话,会产生副作用,所以对于这种真实的对象,需要使用doReturn来打桩。


emmm,这边我也是根据javadoc翻译的,具体的例子:

  /**


   * @since 2022/1/21


   */


  public class MockitoDoReturnTest {


      @Spy


      List<String> spyList = new LinkedList<>();


      @Mock


      List<String> mockList;


      @Before


      public void setUp() {


          MockitoAnnotations.initMocks(this);


      }


      @Test


      public void doReturnTest() {


          // Mockito.when(spyList.get(0)).thenReturn("Hello");


          // Assert.assertEquals(spyList.get(0), "Hello");


          // 这边会抛出java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 异常


          // spyList其实是一个空列表


          Mockito.doReturn("Hello").when(spyList).get(0);


          Assert.assertEquals(spyList.get(0), "Hello");


          Mockito.doReturn("World").when(mockList).get(1);


          Assert.assertEquals(mockList.get(1), "World");


      }


  }




  Mockito中的行为测试


  使用verify()方法可以验证mock的方法是否满足指定的条件,比如可以验证传入的是否是自己指定的参数,该方法调用了几次等等。这种测试被称作行为测试,行为测试不会影响方法调用的接口,但是会


检查是否使用了正确的参数调用方法。

  示例:


  /**


   * @since 2022/1/21


   */


  public class MockitoVerifyTest {


      @Mock


      private Dog dog;



      @Before


      public void setUp() {


          MockitoAnnotations.initMocks(this);


      }


      @Test


      public void testVerify() {


          dog.setAge(22);


          dog.setName("Test1");


          dog.setName("Test2");


          // 验证是否设置了age为22


          Mockito.verify(dog).setAge(ArgumentMatchers.eq(22));


          // 验证是否调用了2次setName("Test1")方法


          Mockito.verify(dog, Mockito.times(1)).setName("Test1");


          // 验证是否重来没有调用过指定的方法


          Mockito.verify(dog, Mockito.never()).getAge();


          Mockito.verify(dog, Mockito.never()).setName("Test3");


          // 验证最后一次的mock方法是否是setName("Test2")


          Mockito.verify(dog, Mockito.atLeast(1)).setName("Test2");


      }



      @Data

      static class Dog {


          private int age;


          private String name;


      }


  }




  使用@InjectMocks进行依赖注入


  之前第一个例子说明过@InjectMocks注解的用法,Mockito可以通过构造函数,setter方法或者属性根据类型来注入mock的对象,下面再提供一个示例。


  代码示例:


  /**


   * @since 2022/1/21


   */


  @Setter


  public class AccountService {


      // 依赖accountDao以及personDao对象


      private AccountDao accountDao;


      private PersonDao personDao;


      public double getBalance() {


          return accountDao.getBalance();


      }


      public String getName() {


          return personDao.getName();


      }


  }



  /**

   * @since 2022/1/21


   */


  public class AccountDao {


      public double getBalance() {


          return 0;


      }


  }


  /**


   * @since 2022/1/21


   */


  public class PersonDao {


      public String getName() {


          return "";


      }


  }




  /**


   * @since 2022/1/21


   */


  public class MockitoInjectTest {


      @Before


      public void setUp() {


          MockitoAnnotations.initMocks(this);


      }


      @Mock


      private AccountDao accountDao;


      @Mock


      private PersonDao personDao;


      @InjectMocks


      private AccountService accountService;


      @Test


      public void testInjectMocks() {


          // mock出accountDao以及personDao对象


          Mockito.when(accountDao.getBalance()).thenReturn(55555.0);


          Mockito.when(personDao.getName()).thenReturn("Jack");


          Assert.assertEquals(accountService.getBalance(), 55555.0, 0);


          Assert.assertEquals(accountService.getName(), "Jack");


      }


  }




  对复杂的mock使用Answers


  上面讲到了thenReturn,doReturn都是对mock的方法直接指定了返回的值,对于一些复杂的mock的场景,比如说我们想根据指定的参数来计算出返回值,或者对参数做一个回调,可以使用Answers方



法。

  具体案例:


  /**


   * @since 2022/1/22


   */


  public class MockitoAnswerTest {


      @Test


      public void doAnswerTest() {


          final Car car = Mockito.mock(Car.class);


          Mockito.doAnswer(invocation -> {


              // 根据参数计算返回值


              final String color = invocation.getArgument(0, String.class);


              final String brand = invocation.getArgument(1, String.class);


              return color + " " + brand;


          }).when(car).getName("red", "BMW");


          Assert.assertEquals(car.getName("red", "BMW"), "red BMW");


      }



      static class Car {

          public String getName(String color, String brand) {


              return "";


          }


      }


  }




  结语


  关于Mockito的使用就总结到这边,Mockito其实还有很多的用法,大家要有兴趣可以自己再找一些资料研究研究。






本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

x
回复

使用道具 举报

本版积分规则

关闭

站长推荐上一条 /1 下一条

小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

GMT+8, 2024-3-28 21:18 , Processed in 0.074794 second(s), 25 queries .

Powered by Discuz! X3.2

© 2001-2024 Comsenz Inc.

快速回复 返回顶部 返回列表