你知道吗?Mockito-Java开发的绝佳模拟框架

前言

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

先容

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

mocking的概念

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

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

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

为什么要模拟?

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

!](https://upload-images.jianshu.io/upload_images/15590149-ce6f103528abb3a2?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

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


1.  `import java.util.ArrayList;`

2.  `publicclassCounter{`

3.  `publicCounter() {`

4.  `}`

5.  `publicint count(ArrayList items) {`

6.  `int results = 0;`

7.  `for(Object curItem : items) {`

8.  `results ++;`

9.  `}`

10.  `return results;`

11.  `}`

12.  `}`

我知道上面的示例与您所获得的一样简单,但是它说明了这一点。如果要测试计数方法,你可以编写测试用例。您并不是要测试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],因此减少或最小化了耦合。此区别功能的结果是更简单的测试代码,应该更易于阅读和修改。

您可以验证交互:


1.  `// mock creation`

2.  `List mockedList = mock(List.class);`

3.  `<span class="Apple-tab-span" style="white-space: pre;">    `

4.  `</span>// using mock object`

5.  `mockedList.add("one");`

6.  `mockedList.clear();`

7.  `// selective and explicit vertification`

8.  `verify(mockedList).add("one");`

9.  `verify(mockedList).clear();  `

或存根方法调用

1.  `// you can mock concrete class, not only interfaces`

2.  `LinkedList mockedList = mock(LinkedList.class);`

3.  `<span class="Apple-tab-span" style="white-space: pre;">    `

4.  `</span>// stubbing - before execution`

5.  `when(mockedList.get(0)).thenReturn("first");..`

6.  `<span class="Apple-tab-span" style="white-space: pre;">    `

7.  `</span>// following prints "first"`

8.  `System.out.println(mockedList.get(0));`

9.  `<span class="Apple-tab-span" style="white-space: pre;">    `

10.  `</span>// following prints "null" because get(999) was not stubbed.`

11.  `System.out.println(mockedList.get(999));`

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

没有模拟框架

使用Mockito框架

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

定义 pom.xml如下:


1.  `<?xml version="1.0" encoding="UTF-8"?>`

2.  `<pre><projectxmlns="http://maven.apache.org/POM/4.0.0"` 

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

4.  `xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">`

5.  `<modelVersion>4.0.0</modelVersion>`

6.  `<groupId>vn.com.phatbeo.ut.mockito.demo</groupId>`

7.  `<artifactId>demoMockito</artifactId>`

8.  `<version>0.0.1-SNAPSHOT</version>`

9.  `<packaging>jar</packaging>`

10.  `<name>demoMockito</name>`

11.  `<url>http://maven.apache.org</url>`

12.  `<properties>`

13.  `<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>`

14.  `</properties>`

15.  `<build>`

16.  `<sourceDirectory>src</sourceDirectory>`

17.  `<testSourceDirectory>test</testSourceDirectory>`

18.  `<plugins>`

19.  `<plugin>`

20.  `<groupId>org.apache.maven.plugins</groupId>`

21.  `<artifactId>maven-compiler-plugin</artifactId>`

22.  `<version>2.3.1</version>`

23.  `<configuration>`

24.  `<source>1.6</source>`

25.  `<target>1.6</target>`

26.  `</configuration>`

27.  `</plugin>`

28.  `</plugins>`

29.  `</build>`

30.  `<dependencies>`

31.  `<dependency>`

32.  `<groupId>junit</groupId>`

33.  `<artifactId>junit</artifactId>`

34.  `<version>4.8.1</version>`

35.  `<scope>test</scope>`

36.  `</dependency>`

37.  `<dependency>`

38.  `<groupId>org.mockito</groupId>`

39.  `<artifactId>mockito-all</artifactId>`

40.  `<version>1.8.5</version>`

41.  `<scope>test</scope>`

42.  `</dependency>`

43.  `</dependencies>`

44.  `</project>` 

步骤2:添加Java源代码

Person.java


1.  `package vn.com.enclave.phatbeo.ut.mockito.demo;`

2.  `/**`

3.  `* @author Phat (Phillip) H. VU <vuhongphat@hotmail.com>`

4.  `*`

5.  `*/` 

6.  `publicclassPerson`

7.  `{`

8.  `privatefinalInteger personID;`

9.  `privatefinalString personName;`

10.  `publicPerson( Integer personID, String personName )`

11.  `{`

12.  `this.personID = personID;`

13.  `this.personName = personName;`

14.  `}`

15.  `publicInteger getPersonID()`

16.  `{`

17.  `return personID;`

18.  `}`

19.  `publicString getPersonName()`

20.  `{`

21.  `return personName;`

22.  `}`

23.  `}`

接口 PersonDAO.java


1.  `package vn.com.enclave.phatbeo.ut.mockito.demo;`

2.  `/**`

3.  `* @author Phat (Phillip) H. VU <vuhongphat@hotmail.com>`

4.  `*`

5.  `*/`

6.  `publicinterfacePersonDao`

7.  `{`

8.  `publicPerson fetchPerson( Integer personID );`

9.  `publicvoid update( Person person );`

10.  `}`

PersonService.java


1.  `package vn.com.enclave.phatbeo.ut.mockito.demo;`

2.  `/**`

3.  `* @author Phat (Phillip) H. VU <vuhongphat@hotmail.com>`

4.  `*`

5.  `*/`

6.  `publicclassPersonService`

7.  `{`

8.  `privatefinalPersonDao personDao;`

9.  `publicPersonService( PersonDao personDao )`

10.  `{`

11.  `this.personDao = personDao;`

12.  `}`

13.  `publicboolean update( Integer personId, String name )`

14.  `{`

15.  `Person person = personDao.fetchPerson( personId );`

16.  `if( person != null)`

17.  `{`

18.  `Person updatedPerson = newPerson( person.getPersonID(), name );`

19.  `personDao.update( updatedPerson );`

20.  `returntrue;`

21.  `}`

22.  `else`

23.  `{`

24.  `returnfalse;`

25.  `}`

26.  `}`

27.  `}`

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

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

假设类 PersionServiceTest.java如下:


1.  `package vn.com.enclave.phatbeo.ut.mockito.demo.test;`

2.  `importstatic org.junit.Assert.assertEquals;`

3.  `importstatic org.junit.Assert.assertFalse;`

4.  `importstatic org.junit.Assert.assertTrue;`

5.  `importstatic org.mockito.Mockito.verify;`

6.  `importstatic org.mockito.Mockito.verifyNoMoreInteractions;`

7.  `importstatic org.mockito.Mockito.verifyZeroInteractions;`

8.  `importstatic org.mockito.Mockito.when;`

9.  `import org.junit.Before;`

10.  `import org.junit.Test;`

11.  `import org.mockito.ArgumentCaptor;`

12.  `import org.mockito.Mock;`

13.  `import org.mockito.MockitoAnnotations;`

14.  `/**`

15.  `* @author Phat (Phillip) H. VU <vuhongphat@hotmail.com>`

16.  `*`

17.  `*/`

18.  `publicclassPersonServiceTest`

19.  `{`

20.  `@Mock`

21.  `privatePersonDao personDAO;`

22.  `privatePersonService personService;`

23.  `@Before`

24.  `publicvoid setUp()`

25.  `throwsException`

26.  `{`

27.  `MockitoAnnotations.initMocks( this);`

28.  `personService = newPersonService( personDAO );`

29.  `}`

30.  `@Test`

31.  `publicvoid shouldUpdatePersonName()`

32.  `{`

33.  `Person person = newPerson( 1, "Phillip");`

34.  `when( personDAO.fetchPerson( 1) ).thenReturn( person );`

35.  `boolean updated = personService.update( 1, "David");`

36.  `assertTrue( updated );`

37.  `verify( personDAO ).fetchPerson( 1);`

38.  `ArgumentCaptor<Person> personCaptor = ArgumentCaptor.forClass( Person.class);`

39.  `verify( personDAO ).update( personCaptor.capture() );`

40.  `Person updatedPerson = personCaptor.getValue();`

41.  `assertEquals( "David", updatedPerson.getPersonName() );`

42.  `// asserts that during the test, there are no other calls to the mock object.`

43.  `verifyNoMoreInteractions( personDAO );`

44.  `}`

45.  `@Test`

46.  `publicvoid shouldNotUpdateIfPersonNotFound()`

47.  `{`

48.  `when( personDAO.fetchPerson( 1) ).thenReturn( null);`

49.  `boolean updated = personService.update( 1, "David");`

50.  `assertFalse( updated );`

51.  `verify( personDAO ).fetchPerson( 1);`

52.  `verifyZeroInteractions( personDAO );`

53.  `verifyNoMoreInteractions( personDAO );`

54.  `}`

55.  `}`

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

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