我是靠谱客的博主 懦弱羽毛,最近开发中收集的这篇文章主要介绍mockito 外部接口_mockito 初识,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

为什么需要模拟?

在我们一开始学编程时,我们所写的对象通常都是独立的。hello world之类的类并不依赖其他的类(System.out除外),也不会操作别的类。但实际上软件中是充满依赖关系的。我们会基于service类写操作类,而service类又是基于数据访问类(DAOs)的,依次下去。

图1 类的依赖关系

单元测试的思路就是我们想在不涉及依赖关系的情况下测试代码。这种测试可以让你无视代码的依赖关系去测试代码的有效性。核心思想就是如果代码按设计正常工作,并且依赖关系也正常,那么他们应该会同时工作正常。

模拟对象的概念就是我们想要创建一个可以替代实际对象的对象。这个模拟对象要可以通过特定参数调用特定的方法,并且能返回预期结果。

模拟有哪些关键点?

在谈到模拟时,你只需关心三样东西:设置测试数据,设定预期结果,验证结果。一些单元测试方案根本就不涉及这些,有的只涉及设置测试数据,有的只涉及设定预期结果和验证。

Stubbing (桩)

Stubbing就是告诉fake当与之交互时执行何种行为过程。通常它可以用来提供那些测试所需的公共属性(像getters和setters)和公共方法。

当谈到stubbing方法,通常你有一系列的选择。或许你希望返回一个特殊的值,抛出一个错误或者触发一个事件,此外,你可能希望指出方法被调用时的不同行为(即通过传递匹配的类型或者参数给方法)。

这咋一听起来工作量很大,但通常并非这样。许多mocking框架的一个重要功能就是你不需要提供stub 的实体方法,也不用在执行测试期间stub那些未被调用的方法或者未使用的属性。

设置预期

Fake的一个关键的特性就是当你用它进行模拟测试时你能够告诉它你预期的结果。例如,你可以要求一个特定的函数被准确的调用3次,或不被调用,或调用至少两次但不超过5次,或者需要满足特定类型的参数、特定值和以上任意的组合的调用。可能性是无穷的。

通过设定预期结果告诉fake你期望发生的事情。因为它是一个模拟测试,所以实际上什么也没发生。但是,对于被测试的类来说,它并无法区分这种情况。所以fake能够调用函数并让它做它该做的。

值得注意的是,大多数模拟框架除了可以创建接口的模拟测试外,还可以创建公有类的模拟测试。

验证预期结果

设置预期和验证预期是同时进行的。设置预期在调用测试类的函数之前完成,验证预期则在它之后。所以,首先你设定好预期结果,然后去验证你的预期结果是否正确。

在一个单元测试中,如果你设定的预期没有得到满足,那么这个单元测试就是失败了。例如,你设置预期结果是 ILoginService.login函数必须用特定的用户名和密码被调用一次,但是在测试中它并没有被调用,这个fake没被验证,所以测试失败。

模拟的好处是什么?

提前创建测试; TDD(测试驱动开发)

这是个最大的好处吧。如果你创建了一个Mock那么你就可以在service接口创建之前写Service Tests了,这样你就能在开发过程中把测试添加到你的自动化测试环境中了。换句话说,模拟使你能够使用测试驱动开发。

团队可以并行工作

这类似于上面的那点;为不存在的代码创建测试。但前面讲的是开发人员编写测试程序,这里说的是测试团队来创建。当还没有任何东西要测的时候测试团队如何来创建测试呢?模拟并针对模拟测试!这意味着当service借口需要测试时,实际上QA团队已经有了一套完整的测试组件;没有出现一个团队等待另一个团队完成的情况。这使得模拟的效益型尤为突出了。

你可以创建一个验证或者演示程序。

由于Mocks非常高效,Mocks可以用来创建一个概念证明,作为一个示意图,或者作为一个你正考虑构建项目的演示程序。这为你决定项目接下来是否要进行提供了有力的基础,但最重要的还是提供了实际的设计决策。

为无法访问的资源编写测试

这个好处不属于实际效益的一种,而是作为一个必要时的“救生圈”。有没有遇到这样的情况?当你想要测试一个service接口,但service需要经过防火墙访问,防火墙不能为你打开或者你需要认证才能访问。遇到这样情况时,你可以在你能访问的地方使用MockService替代,这就是一个“救生圈”功能。

Mock 可以分发给用户

在有些情况下,某种原因你需要允许一些外部来源访问你的测试系统,像合作伙伴或者客户。这些原因导致别人也可以访问你的敏感信息,而你或许只是想允许访问部分测试环境。在这种情况下,如何向合作伙伴或者客户提供一个测试系统来开发或者做测试呢?最简单的就是提供一个mock,无论是来自于你的网络或者客户的网络。soapUI mock非常容易配置,他可以运行在soapUI或者作为一个war包发布到你的java服务器里面。

隔离系统

有时,你希望在没有系统其他部分的影响下测试系统单独的一部分。由于其他系统部分会给测试数据造成干扰,影响根据数据收集得到的测试结论。使用mock你可以移除掉除了需要测试部分的系统依赖的模拟。当隔离这些mocks后,mocks就变得非常简单可靠,快速可预见。这为你提供了一个移除了随机行为,有重复模式并且可以监控特殊系统的测试环境。

Mockito 框架

Mockito 是一个基于MIT协议的开源java测试框架。

Mockito区别于其他模拟框架的地方主要是允许开发者在没有建立“预期”时验证被测系统的行为。对mock对象的一个批评是测试代码与被测系统高度耦合,由于Mockito试图通过移除“期望规范”来去除expect-run-verify模式(期望--运行--验证模式),因此使耦合度降低到最低。这样的突出特性简化了测试代码,使它更容易阅读和修改了。

图2 不使用Mock框架

图3 使用Mockito框架

步骤 1:  在IDE中创建一个普通的Java项目

在Eclipse、NetBeans或IntelliJ IDEA中创建一个普通的Java项目。

步骤 2:  添加java源码

类Person.java:

packagemockitodemo;public classPerson

{private finalInteger personID;private finalString personName;publicPerson( Integer personID, String personName )

{this.personID =personID;this.personName =personName;

}publicInteger getPersonID()

{returnpersonID;

}publicString getPersonName()

{returnpersonName;

}

}

View Code

接口PersonDAO.java:

packagemockitodemo;public interfacePersonDao

{publicPerson fetchPerson( Integer personID );public voidupdate( Person person );

}

View Code

类PersonService.java:

packagemockitodemo;public classPersonService

{private finalPersonDao personDao;publicPersonService( PersonDao personDao )

{this.personDao =personDao;

}public booleanupdate( Integer personId, String name )

{

Person person=personDao.fetchPerson( personId );if( person != null)

{

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

personDao.update( updatedPerson );return true;

}else{return false;

}

}

}

View Code

步骤 3:  添加单元测试类.

接下来为类PersonService.java创建单元测试用例。我们使用JUnit 4.x和Mockito 1.9.5。可以设计测试用例类PersionServiceTest.java为如下,代码中有详细注释说明:

packagemockitodemo;importorg.junit.After;importorg.junit.AfterClass;importorg.junit.Before;importorg.junit.BeforeClass;importorg.junit.Test;import static org.junit.Assert.*;importorg.mockito.Mock;importorg.mockito.MockitoAnnotations;importorg.mockito.ArgumentCaptor;import static org.mockito.Mockito.*;/*** PersonService的单元测试用例

*

*@authorjackzhou*/

public classPersonServiceTest {

@Mockprivate PersonDao personDAO; //模拟对象

private PersonService personService; //被测类

publicPersonServiceTest() {

}

@BeforeClasspublic static voidsetUpClass() {

}

@AfterClasspublic static voidtearDownClass() {

}//在@Test标注的测试方法之前运行

@Beforepublic void setUp() throwsException {//初始化测试用例类中由Mockito的注解标注的所有模拟对象

MockitoAnnotations.initMocks(this);//用模拟对象创建被测类对象

personService = newPersonService(personDAO);

}

@Afterpublic voidtearDown() {

}

@Testpublic voidshouldUpdatePersonName() {

Person person= new Person(1, "Phillip");//设置模拟对象的返回预期值

when(personDAO.fetchPerson(1)).thenReturn(person);//执行测试

boolean updated = personService.update(1, "David");//验证更新是否成功

assertTrue(updated);//验证模拟对象的fetchPerson(1)方法是否被调用了一次

verify(personDAO).fetchPerson(1);//得到一个抓取器

ArgumentCaptor personCaptor = ArgumentCaptor.forClass(Person.class);//验证模拟对象的update()是否被调用一次,并抓取调用时传入的参数值

verify(personDAO).update(personCaptor.capture());//获取抓取到的参数值

Person updatePerson =personCaptor.getValue();//验证调用时的参数值

assertEquals("David", updatePerson.getPersonName());//asserts that during the test, there are no other calls to the mock object.//检查模拟对象上是否还有未验证的交互

verifyNoMoreInteractions(personDAO);

}

@Testpublic voidshouldNotUpdateIfPersonNotFound() {//设置模拟对象的返回预期值

when(personDAO.fetchPerson(1)).thenReturn(null);//执行测试

boolean updated = personService.update(1, "David");//验证更新是否失败

assertFalse(updated);//验证模拟对象的fetchPerson(1)方法是否被调用了一次

verify(personDAO).fetchPerson(1);//验证模拟对象是否没有发生任何交互

verifyZeroInteractions(personDAO);//检查模拟对象上是否还有未验证的交互

verifyNoMoreInteractions(personDAO);

}/*** Test of update method, of class PersonService.*/@Testpublic voidtestUpdate() {

System.out.println("update");

Integer personId= null;

String name= "Phillip";

PersonService instance= new PersonService(newPersonDao() {

@OverridepublicPerson fetchPerson(Integer personID) {

System.out.println("Not supported yet.");return null;

}

@Overridepublic voidupdate(Person person) {

System.out.println("Not supported yet.");

}

});boolean expResult = false;boolean result =instance.update(personId, name);

assertEquals(expResult, result);//TODO review the generated test code and remove the default call to fail.

fail("The test case is a prototype.");

}

}

View Code

这里setUpClass()、tearDownClass()、setUp()、tearDown()称为测试夹具(Fixture),就是测试运行程序(test runner)在运行测试方法之前进行初始化、或之后进行回收资源的工作。JUnit 4之前是通过setUp、tearDown方法完成。在JUnit 4中,仍然可以在每个测试方法运行之前初始化字段和配置环境,当然也是通过注解完成。在JUnit 4中,通过@Before标注setUp方法;@After标注tearDown方法。在一个测试类中,甚至可以使用多个@Before来注解多个方法,这些方法都是在每个测试之前运行。说明一点,一个测试用例类可以包含多个打上@Test注解的测试方法,在运行时,每个测试方法都对应一个测试用例类的实例。@Before是在每个测试方法运行前均初始化一次,同理@Ater是在每个测试方法运行完毕后均执行一次。也就是说,经这两个注解的初始化和注销,可以保证各个测试之间的独立性而互不干扰,它的缺点是效率低。另外,不需要在超类中显式调用初始化和清除方法,只要它们不被覆盖,测试运行程序将根据需要自动调用这些方法。超类中的@Before方法在子类的@Before方法之前调用(与构造函数调用顺序一致),@After方法是子类在超类之前运行。

这里shouldUpdatePersonName()、shouldNotUpdateIfPersonNotFound()和testUpdate()都是测试PersonService的update()方法,它依赖于PersonDao接口。前两者使用了模拟测试。testUpdate()则没有使用模拟测试。下面是测试结果:

图4 测试结果点击打开链接

可以看出,使用模拟测试的两个测试成功了,没有使用模拟测试的testUpdate()失败。对于模拟测试,在测试用例类中要先声明依赖的各个模拟对象,在setUp()中用MockitoAnnotations.initMocks()初始化所有模拟对象。在进行模拟测试时,要先设置模拟对象上方法的返回预期值,执行测试时会调用模拟对象上的方法,因此要验证这些方法是否被调用,并且传入的参数值是否符合预期。对于testUpdate()测试,我们需要自己创建测试PersonService.update()所需的所有PersonDao数据,因为我们只知道公开的PersonDao接口,其具体实现类(比如从数据库中拿真实的数据,或写入到数据库中)可能由另一个团队在负责,以适配不同的数据库系统。这样的依赖关系无疑使单元测试比较麻烦,而要拿真正PersonDao实现来进行测试,那也应该是后期集成测试的任务,把不同的组件集成到一起在真实环境中测试。有了模拟测试框架,就可以最大限度地降低单元测试时的依赖耦合性。

最后

以上就是懦弱羽毛为你收集整理的mockito 外部接口_mockito 初识的全部内容,希望文章能够帮你解决mockito 外部接口_mockito 初识所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(62)

评论列表共有 0 条评论

立即
投稿
返回
顶部