单元测试系列之Mockito详解(上)

发表于:2022-3-14 09:57

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:大师傅姑爷    来源:稀土掘金

  简介
  所谓mock就是创建一个类的虚假对象,在测试环境中,替换掉真实对象,以达到以下目的:
  ·验证这个对象的某个方法的调用情况,比如调用了多少次、参数是什么等等;
  · 指定这个对象的某些方法的行为,比如返回特定的值、执行特定的动作等。
  要使用Mock,一般需要用到mock框架,本文介绍Mockito这个框架,这个是Java界使用最广泛的一个mock框架。
  比如测试用户登陆:
  public class LoginPresenter {
   
     private UserManager userManager = new UserManager();
   
     public void login(String username, String password) {
         if (username == null || username.length() == 0) return;
         if (password == null || password.length() < 6) return;
         userManager.performLogin(username, password);
    }
   
     public void setUserManager(UserManager userManager) {
         this.userManager = userManager;
    }
  }

  对应的测试类:
  public class LoginPresenterTest {
     LoginPresenter loginPresenter;
   
     @Before
     public void setUp() throws Exception {
         loginPresenter = new LoginPresenter();
    }
   
  @Test
     public void testLogin() {
         UserManager mockUserManager = Mockito.mock(UserManager.class);
         //loginPresenter中使用的UserManager必须是Mock出来的对象,只有这样才能验证UserManager.performLogin,
         // 所以需要将mock出来的对象传给loginPresenter
         loginPresenter.setUserManager(mockUserManager);
         loginPresenter.login("aya", "123456");
         //verify传入的必须mock出来的对象,而且必须与loginPresenter中使用的UserManager对象是同一个
         Mockito.verify(mockUserManager).performLogin("aya", "123456");
    }
  }

  注意
  Mockito.mock()并不是mock一整个类,而是根据传进去的一个类,mock出属于这个类的一个对象,并且返回这个mock对象;而传进去的这个类本身并没有改变,用这个类new出来的对象也没有受到任何改变!
  比如这样写就会出错:
  public class LoginPresenterTest {
  @Test
     public void testLogin() {
         Mockito.mock(UserManager.class);
         UserManager userManager = loginPresenter.getUserManager();
         loginPresenter.login("aya", "123456");
         Mockito.verify(userManager).performLogin("aya", "123456");
    }
  }

  mock出来的对象并不会自动替换掉正式代码里面的对象,你必须要有某种方式把mock对象应用到正式代码里面,上面例子中使用setUserManager()传入了mock的对象。所以下面这个也是个错误的例子:
  public class LoginPresenterTest {
     @Test
     public void testLogin(){
         UserManager mockUserManager = Mockito.mock(UserManager.class); 
         LoginPresenter loginPresenter = new LoginPresenter();
         loginPresenter.login("aya", "123456");
         Mockito.verify(mockUserManager).performLogin("aya", "123456"); 
    }
  }

  使用setter的方式传入mock对象时,如果该setter方法其他地方没用到,只是为了测试而增加的,不是很优雅。可以使用依赖注入的方式来解决,比如把UserManager作为LoginPresenter的构造函数的参数,最终如下:
  public class LoginPresenter {
   
     private UserManager mUserManager;
   
     public LoginPresenter(UserManager userManager){
         mUserManager = userManager;
    }
     
     public void login(String username, String password) {
         if (username == null || username.length() == 0) return;
         if (password == null || password.length() < 6) return;
         mUserManager.performLogin(username, password);
    }
  }
   
  public class LoginPresenterTest {
     LoginPresenter loginPresenter;
     @Test
     public void testLogin() {
         UserManager mockUserManager = Mockito.mock(UserManager.class);
         //loginPresenter中使用的UserManager必须是Mock出来的对象,只有这样才能验证UserManager.performLogin,
         // 所以需要将mock出来的对象传给loginPresenter
         loginPresenter = new LoginPresenter(mockUserManager);
         loginPresenter.login("aya", "123456");
         //verify传入的必须mock出来的对象,而且必须与loginPresenter中使用的UserManager对象是同一个
         Mockito.verify(mockUserManager).performLogin("aya", "123456");
    }
  }

  验证方法调用
  查看Mockito源码,可以看到verify()还有一个重载的方法:
     /**
      * Verifies certain behavior happened at least once / exact number of times / never. E.g:
      * <pre class="code"><code class="java">
      *   verify(mock, times(5)).someMethod("was called five times");
      *
      *   verify(mock, atLeast(2)).someMethod("was called at least two times");
      *
      *   //you can use flexible argument matchers, e.g:
      *   verify(mock, atLeastOnce()).someMethod(<b>anyString()</b>);
      * </code></pre>
      *
      * <b>times(1) is the default</b> and can be omitted
      * <p>
      * Arguments passed are compared using <code>equals()</code> method.
      * Read about {@link ArgumentCaptor} or {@link ArgumentMatcher} to find out other ways of matching / asserting arguments passed.
      * <p>
      *
      * @param mock to be verified
      * @param mode times(x), atLeastOnce() or never()
      *
      * @return mock object itself
      */
     @CheckReturnValue
     public static <T> T verify(T mock, VerificationMode mode) {
         return MOCKITO_CORE.verify(mock, mode);
    }

  其中VerificationMode源码如下:
  /**
  * Allows verifying that certain behavior happened at least once / exact number
  * of times / never. E.g:
  *
  * <pre class="code"><code class="java">
  * verify(mock, times(5)).someMethod(&quot;was called five times&quot;);
  *
  * verify(mock, never()).someMethod(&quot;was never called&quot;);
  *
  * verify(mock, atLeastOnce()).someMethod(&quot;was called at least once&quot;);
  *
  * verify(mock, atLeast(2)).someMethod(&quot;was called at least twice&quot;);
  *
  * verify(mock, atMost(3)).someMethod(&quot;was called at most 3 times&quot;);
  *
  * </code></pre>
  *
  * <b>times(1) is the default</b> and can be omitted
  * <p>
  * See examples in javadoc for {@link Mockito#verify(Object, VerificationMode)}
  */
  public interface VerificationMode {
   
     /**
      * Performs the verification
      */
     void verify(VerificationData data);
   
     /**
      * Description will be prepended to the assertion error if verification fails.
      * @param description The custom failure message
      * @return VerificationMode
      * @since 2.1.0
      */
     VerificationMode description(String description);
  }
   

  可以看出,在验证方法调用时,可以使用times()指定调用次数,使用never()验证从未调用,使用 atLeastOnce()验证至少调用了一次,使用atLeast()验证至少调用次数,使用atMost()验证最多调用次数。
  如果你不关心传入的参数,只关心方法是否调用,Mockito也提供了一系列的any方法,来表示任何的参数都行:
  Mockito.verify(mockUserManager).performLogin(Mockito.anyString(), Mockito.anyString());

  Mockito.anyString()表示任意非null字符串,类似的有anyFloat(),anyBoolean,anyByte(),anyLong(),也提供了anyList,anyListOf(Class<T> clazz),anySet(),anyMap(),anyCollection等方法,甚至isNotNull(),isNotNull(Class<T> clazz),anyObject,更多方法去看源码。

  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

精彩评论

  • nicou0
    2022-3-14 23:29:35

    这个是Java界使用最广泛的一个mock框架。

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号