你一定要知道的Mockito-Java开发的绝佳模拟框架

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了你一定要知道的Mockito-Java开发的绝佳模拟框架相关的知识,希望对你有一定的参考价值。

技术图片技术图片技术图片技术图片# Mockito-Java开发的绝佳模拟框架。

--Peter 2020-02-25

Mockito是一个非常不错的模拟框架。它使您可以使用简洁的API编写漂亮的测试。

介绍

本文将展示模拟框架的一些基本概念,为什么我们应该使用它,并逐步介绍一下在Java中应用Mockito的简单方法。

mocking的概念

在软件开发领域之外,术语“ mock”表示模仿或模仿。mock因此,可以被认为是替身,冒名顶替者或与软件开发有关的最常见称呼fake

伪造通常用作受测类依赖项的替身。

术语和定义
依赖关系 –依赖关系是指应用程序中的一个类为了执行其预期功能而依赖于另一个类。依赖关系通常存储在依赖类内部的实例变量中。
被测类 –编写单元测试时,术语“单元”通常是指单个类,尤其是针对其编写测试的类。因此,被测类是被测应用程序类。

为什么要模拟?

当我们学习编程时,我们的对象通常是独立的。任何动作都不依赖外部类(除了System.out),在学习语言的过程中我们编写的许多其他类也没有依赖。但是,在现实世界中,软件具有依赖性。我们有依赖于服务的操作类和依赖于数据访问对象(DAO)的服务,并且列表继续存在。

? 技术图片

单元测试的思想是我们要测试代码而不测试依赖项。该测试使您可以验证所测试的代码是否有效,无论其是否依赖。从理论上讲,如果我编写的代码按设计工作,而我的依赖项按设计工作,那么它们应该按设计一起工作。下面的代码将是一个示例:

import java.util.ArrayList;
public class Counter {
     public Counter() {
     }
     public int count(ArrayList items) {
          int results = 0;
          for(Object curItem : items) {
               results ++;
          }
          return results;
     }
} 

我知道上面的示例与您所获得的一样简单,但是它说明了这一点。如果要测试计数方法,你可以编写测试用例。您并不是要测试ArrayList的工作原理,您唯一的目标是测试对ArrayList的使用。

模拟背后的概念是我们要创建一个代替真实对象的模拟对象。对该模拟对象调用某些方法,它将能返回期望的结果。

模拟概念是什么?

当涉及到mocking时,您只需要关心三件事:存根,设定期望并进行验证。 一些单元测试方案不涉及其中任何一个,其他仅涉及存根,而其他涉及设置期望和验证。

存根

存根是告诉您的假货与之互动时如何表现的过程。通常,您可以对公共属性(具有getter和/或setter的属性)和公共功能进行存根。

当涉及到存根函数时,通常会有很多选择。您可能希望返回特定的值,抛出错误或调度事件。此外,您可能希望指出函数的行为取决于调用方式的不同(即,通过匹配传递给函数的参数的类型或值)。

如果听起来需要做很多工作,可以,但通常不是。许多模拟框架的一大特点是您不需要对void函数进行存根。您也不必在测试执行过程中对任何未调用的功能或未查询的属性进行存根。

设定期望

伪造品的主要功能之一是能够在测试运行时告诉伪造品您期望的内容。例如,您可能希望特定的函数被正确调用3次。您可能希望它永远不会被调用。您可能希望至少调用两次,但不要超过5次。您可能希望使用特定类型的参数或特定值或以上任意组合来调用它。可能性是无止境。

设定期望是告诉您的假货您预期会发生什么的过程。请记住,由于这是假货,因此实际上没有任何反应。但是,您正在测试的课程绝不明智。从它的角度来看,它调用了该函数并期望它完成了应该执行的操作。

值得一提的是,大多数模拟框架都允许您创建接口或公共类的模拟。您不仅限于仅模拟接口。

验证中

设定期望和验证是齐头并进的。设置期望值是在调用被测类上的函数之前完成的。之后进行验证。因此,首先要设定期望,然后验证是否满足期望。

从单元测试的角度来看,如果未达到您的期望,则单元测试将失败。例如,如果您期望应该使用特定的用户名和密码仅一次调用ILoginService.login函数,但是在执行测试期间从未调用过该函数,则伪造品将无法验证,并且测试应失败。

mocking有什么好处?

技术图片 您可以预先创建测试;TDD

? 这是更强大的好处之一。如果创建了Mock,则可以在创建服务之前编写服务测试,从而使您能够在开发过程中将测试添加到自动化环境中。换句话说,服务模拟使您能够使用测试驱动开发。

技术图片 团队可以并行工作

? 这类似于上面的内容;为不存在的代码创建测试。但是上一点是针对编写测试的开发人员的,这一点是针对测试团队的。当您没有要测试的东西时,团队如何开始创建测试?模拟它并针对模拟编写测试!这意味着质量保证团队实际上可以在准备好要测试服务时准备一整套测试。当一个团队等待另一个团队完成时,我们没有停机时间。这使得嘲弄的财务论点特别强烈。

技术图片 您可以创建概念或演示的证明。

? 由于Mocks可以(非常容易地完成)制造,因此非常经济高效,因此可以将Mocks用于创建概念验证,线框或正在考虑构建的产品的演示。这非常强大,为决策是否继续进行开发项目提供了良好的基础,但最重要的是可以进行实际的设计决策。

技术图片 您可以为无法访问的资源编写测试

? 这是不属于实际福利类别的福利之一,而是可以起到挽救生命的作用。您是否曾经想测试或使用一项服务,却只是被告知该服务位于防火墙后面,并且无法为您打开该防火墙或您已被授权使用该服务?当您这样做时,将MockService放置在可访问的位置(包括您的本地计算机上)可以节省生命。

技术图片 模拟可以交付给客户

? 在某些情况下,有某些原因导致您不允许外部资源(例如合作伙伴或客户)访问测试系统。这些原因可能是访问安全性,信息的敏感性,或者仅仅是测试环境可能无法24/7全天候访问的事实。在这些情况下;您如何为合作伙伴或客户提供测试系统以开始开发或测试?一个简单的解决方案是从您的网络或客户自己的网络提供模拟。soapUI模拟程序非常易于部署,它既可以在soapUI中运行,也可以作为.WAR文件导出并放置在您选择的Java服务器中。

技术图片 您可以隔离系统

? 有时,您希望测试系统的一部分,而不会影响其他系统部分。这是因为其他系统会给测试数据增加噪音,并使从收集到的数据中得出良好的结论变得更加困难。使用模拟,您可以删除模拟所有系统的所有依赖项,但您需要在测试中精确定位的系统除外。在进行模拟隔离时,可以使这些模拟极其简单,但可靠,快速且可预测。这为您提供了一个测试环境,在其中消除了所有随机行为,具有可重复的模式并可以很好地监视特定系统。

Mockito框架

Mockito是根据MIT许可证发布的Java开源测试框架。
Mockito通过允许开发人员验证被测系统(SUT)的行为而无需事先建立期望,从而将自己与其他模拟框架区分开。[4] 对模拟对象的批评之一是测试代码与被测系统之间的耦合更加紧密。[5] 由于Mockito试图通过消除期望值的规范来消除期望-运行-验证模式[6],因此减少或最小化了耦合。此区别功能的结果是更简单的测试代码,应该更易于阅读和修改。

您可以验证交互:

// mock creation
List mockedList = mock(List.class);
<span class="Apple-tab-span" style="white-space: pre;"> 
</span>// using mock object
mockedList.add("one");
mockedList.clear();
// selective and explicit vertification
verify(mockedList).add("one");
verify(mockedList).clear();   

或存根方法调用

// you can mock concrete class, not only interfaces
LinkedList mockedList = mock(LinkedList.class);
<span class="Apple-tab-span" style="white-space: pre;"> 
</span>// stubbing - before execution
when(mockedList.get(0)).thenReturn("first");
<span class="Apple-tab-span" style="white-space: pre;"> 
</span>// following prints "first"
System.out.println(mockedList.get(0));
<span class="Apple-tab-span" style="white-space: pre;"> 
</span>// following prints "null" because get(999) was not stubbed.
System.out.println(mockedList.get(999)); 

一个使用Mockito的简单Java代码示例

技术图片
没有模拟框架

技术图片
使用Mockito框架

步骤1:在Eclipse中创建一个Maven项目

定义pom.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<pre><project xmlns="http://maven.apache.org/POM/4.0.0" 

         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>vn.com.phatbeo.ut.mockito.demo</groupId>
  <artifactId>demoMockito</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>
  <name>demoMockito</name>
  <url>http://maven.apache.org</url>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <build>
    <sourceDirectory>src</sourceDirectory>
    <testSourceDirectory>test</testSourceDirectory>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.1</version>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
      </plugins>
  </build>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-all</artifactId>
      <version>1.8.5</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

</project> 

步骤2:添加Java源代码

Person.java

 package vn.com.enclave.phatbeo.ut.mockito.demo;
/** 
 * @author Phat (Phillip) H. VU <vuhongphat@hotmail.com>
 *  
 */ 
public class Person
{
    private final Integer personID;
    private final String personName;
    public Person( Integer personID, String personName )
    {
        this.personID = personID;
        this.personName = personName;
    }
    public Integer getPersonID()
    {
        return personID;
    }
    public String getPersonName()
    {
        return personName;
    }
} 

接口 PersonDAO.java

package vn.com.enclave.phatbeo.ut.mockito.demo;
/**
 * @author Phat (Phillip) H. VU <vuhongphat@hotmail.com>
 * 
 */
public interface PersonDao
{
    public Person fetchPerson( Integer personID );
    public void update( Person person );
} 

PersonService.java

 package vn.com.enclave.phatbeo.ut.mockito.demo;
/**
 * @author Phat (Phillip) H. VU <vuhongphat@hotmail.com>
 * 
 */
public class PersonService
{
    private final PersonDao personDao;
    public PersonService( PersonDao personDao )
    {
        this.personDao = personDao;
    }
    public boolean update( Integer personId, String name )
    {
        Person person = personDao.fetchPerson( personId );
        if( person != null )
        {
            Person updatedPerson = new Person( person.getPersonID(), name );
            personDao.update( updatedPerson );
            return true;
        }
        else
        {
            return false;
        }
    }
} 

步骤3:添加了单元测试类。

然后,跳转为类编写单元测试用例 PersonService.java

假设类PersionServiceTest.java如下:

package vn.com.enclave.phatbeo.ut.mockito.demo.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
 * @author Phat (Phillip) H. VU <vuhongphat@hotmail.com>
 * 
 */
public class PersonServiceTest
{
    @Mock
    private PersonDao personDAO;
    private PersonService personService;
    @Before
    public void setUp()
        throws Exception
    {
        MockitoAnnotations.initMocks( this );
        personService = new PersonService( personDAO );
    }
    @Test
    public void shouldUpdatePersonName()
    {
        Person person = new Person( 1, "Phillip" );
        when( personDAO.fetchPerson( 1 ) ).thenReturn( person );
        boolean updated = personService.update( 1, "David" );
        assertTrue( updated );
        verify( personDAO ).fetchPerson( 1 );
        ArgumentCaptor<Person> personCaptor = ArgumentCaptor.forClass( Person.class );
        verify( personDAO ).update( personCaptor.capture() );
        Person updatedPerson = personCaptor.getValue();
        assertEquals( "David", updatedPerson.getPersonName() );
        // asserts that during the test, there are no other calls to the mock object.
        verifyNoMoreInteractions( personDAO );
    }
    @Test
    public void shouldNotUpdateIfPersonNotFound()
    {
        when( personDAO.fetchPerson( 1 ) ).thenReturn( null );
        boolean updated = personService.update( 1, "David" );
        assertFalse( updated );
        verify( personDAO ).fetchPerson( 1 );
        verifyZeroInteractions( personDAO );
        verifyNoMoreInteractions( personDAO );
    }
} 

问题: 为什么我们在Java开发测试中使用Mockito?

答:好处有很多,最大的好处是团队可以并行工作

以上是关于你一定要知道的Mockito-Java开发的绝佳模拟框架的主要内容,如果未能解决你的问题,请参考以下文章

Python自学这六大编程开发工具用途你一定要知道

这 9 种单例模式你都会吗?

Xcode7中你一定要知道的炸裂调试神技

Xcode7中你一定要知道的炸裂调试神技

Xcode7中你一定要知道的炸裂调试神技

进阶自动化测试,你一定要知道的...