mockito 初识

转载:http://blog.csdn.net/zhoudaxia/article/details/33056093

在平时的开发工作中,经常会碰到开发进度不一致,导致你要调用的接口还没好,此时又需要把自己的接口提供给其他方,此时需要写一个mock接口给对方调试。或者自己需要测试接口是否有效,可是依赖方还没好。

模拟(Mock)的概念

  在软件开发的世界之外, "mock"一词是指模仿或者效仿。因此可以将“mock”理解为一个替身,替代者。在软件开发中提及"mock",通常理解为模拟对象或者fake。

  译者注:mock等多代表的是对被模拟对象的抽象类,你可以把fake理解为mock的实例。不知道这样说准不准确:)

  Fake通常被用作被测类依赖关系的替代者.。

名词定义  
依赖关系 – 依赖关系是指在应用程序中一个类基于另一个类来执行其预定的功能。依赖关系通常都存在于所依赖的类的实例变量中。
 
被测类 – 在编写单元测试的时候,“单元”一词通常代表一个单独的类及为其编写的测试代码。被测类指的就是其中被测试的类。

为什么需要模拟? 

  在我们一开始学编程时,我们所写的对象通常都是独立的。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:

package mockitodemo;  

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 mockitodemo;  

public interface PersonDao
{
    public Person fetchPerson( Integer personID );
    public void update( Person person );
}   

类PersonService.java:

package mockitodemo;  

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创建单元测试用例。我们使用JUnit 4.x和Mockito 1.9.5。可以设计测试用例类PersionServiceTest.java为如下,代码中有详细注释说明:

package mockitodemo;  

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.ArgumentCaptor;
import static org.mockito.Mockito.*;  

/**
 * PersonService的单元测试用例
 *
 * @author jackzhou
 */
public class PersonServiceTest {  

    @Mock
    private PersonDao personDAO;  // 模拟对象
    private PersonService personService;  // 被测类  

    public PersonServiceTest() {
    }  

    @BeforeClass
    public static void setUpClass() {
    }  

    @AfterClass
    public static void tearDownClass() {
    }  

    // 在@Test标注的测试方法之前运行
    @Before
    public void setUp() throws Exception {
        // 初始化测试用例类中由Mockito的注解标注的所有模拟对象
        MockitoAnnotations.initMocks(this);
        // 用模拟对象创建被测类对象
        personService = new PersonService(personDAO);
    }  

    @After
    public void tearDown() {
    }  

    @Test
    public void shouldUpdatePersonName() {
        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<Person> 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);
    }  

    @Test
    public void shouldNotUpdateIfPersonNotFound() {
        // 设置模拟对象的返回预期值
        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.
     */
    @Test
    public void testUpdate() {
        System.out.println("update");
        Integer personId = null;
        String name = "Phillip";
        PersonService instance = new PersonService(new PersonDao() {  

            @Override
            public Person fetchPerson(Integer personID) {
                System.out.println("Not supported yet.");
                return null;
            }  

            @Override
            public void update(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.");
    }
}  

 

 这里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实现来进行测试,那也应该是后期集成测试的任务,把不同的组件集成到一起在真实环境中测试。有了模拟测试框架,就可以最大限度地降低单元测试时的依赖耦合性。

时间: 2024-10-11 01:48:02

mockito 初识的相关文章

初识Python,望君多多关照

在学习Python之前,我们接触过数据结构和网页制作.前者让我们学习如何把C语言运用的更加整齐规范,而后者让我们亲身学习如何运用所学,制作一个静态网页.通过这些课程的学习,让我对C语言产生了比较大的压力,以至于对编程.对这学期的Python课程都有一种如临大敌的感觉. 但是真的学习了这门课程,体会了编码过程中的一些固定运用方法和套路之后,也许过程中对这门课程隐隐约约产生了一点点朦胧的感觉,仿佛他也并没有想象中的那么困难,起码现在的学习让我认为,他可能没有C语言那么繁琐和麻烦.当然,以一个初学者的

初识数组排序!!!!

<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>初识数组排序</title> <!--调试成功--> <style type="text/css"> *{ padding:0; margin: 0; } li,ul{ list-style: none; } #p

初识操作系统和linux

初识操作系统和linux 1.计算机系统由硬件系统和软件系统两大部分组成:是一种能接收和存储信息,并按照存储在其内部的程序对海量数据进行自动.高速地处理,然后把处理结果输出的现代化智能电子设备. 2.世界上第一台计算机是1946年诞生在美国宾州大学. 3.冯·诺依曼体系结构:1946年数学家冯·诺依曼于提出计算机硬件系统由运算器.控制器.存储器.输入设备.输出设备.摩根定律:当价格不变时,集成电路上可容纳的元器件的数目,约每隔18-24个月便会增加一倍,性能也将提升一倍.现在计算机技术进本很难遵

mockito使用心得

前提:pom引用<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version></dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>sprin

JAVA 初识类加载机制 第13节

JAVA 初识类加载机制 第13节 从这章开始,我们就进入虚拟机类加载机制的学习了.那么什么是类加载呢?当我们写完一个Java类的时候,并不是直接就可以运行的,它还要编译成.class文件,再由虚拟机解释给当前的操作系统去执行.这些过程都是我们看不见的,我们能看见的也就是一个.class文件.既然虚拟机要解释这些.class文件给当前的操作系统听,那么他怎么获得这些.class文件呢?虚拟机获得这些.class文件的过程就是类加载了. 所以,总结来说就是:虚拟机将.class文件从磁盘或者其他地

初识React

原文地址:北云软件-初识React 专注于UI 在MVC分层设计模式中,react常被拿来实现视图层(V).React不依赖于技术栈的其他部分,因此可以方便的在现有项目中尝试用它来实现一个小特性. 虚拟DOM React从DOM中抽象出来,给出一种更简洁的编程模型,且性能表现更好.能够通过NodeJS实现服务端渲染,通过React Native开发原生app. 数据流React实现单向.响应式数据流,减少boilerplate且比传统数据绑定更容易理解. 简洁的组件React的组件都实现了一个r

泛型的几种类型以及初识webform

今天学习的可以分为两类吧,但是学习的都是比较抽象的,不太容易掌握吧.首先我们大部分时间学习了泛型,泛型的委托,泛型接口以及枚举器,迭代器,扩展方法:最后简单的认识了webform,实现了一个简单的功能. 一.泛型 定义:泛型(generic)可以软糖多个类型共享一组代码,泛型允许我们声明类型参数化.可以用不同的类型进行实例化,说白了,就是可以用类型占位符,创建具体类型致命的真实概念.C#中提供了五种泛型,类,结构,接口,委托和方法.下面举例说明可能更容易理解, class MyStack<T>

最新计算机技术与管理科学应用专家——初识ERB

ERB管理系统:英文全称Enterprise Resource and Behavior,英文简称:ERB,中文名全称:企业资源与行为管理系统.ERB是由理文企业管理顾问有限公司首席管理师,现任商翼ERB企业管理系统项目总监吴志华先生,于2010年9月首先提出的.ERB不再单以供应链管理作为系统应用的基础,而是以企业行为与企业资源规划的最佳结合作为系统应用设计的核心基础,强调企业行为的规划.执行.监督与追溯,强调企业管理水平与员工素养的持续提升:提供企业行为与企业资源管理最佳结合的整体应用解决方

[OpenGL]环境搭建以及OpenGL初识

想往游戏行业发展的话,经常被提及到的就是OpenGL和DirectX,这两者听起来感觉是一门挺高深的技术,今天我也开始摸索学习OpenGL,那么OpenGL到底是什么?它和DirectX有什么区别和联系? OpenGL初识 OpenGL只是一套图形函数库 DirectX包含图形.声音.输入.网络等模块. 但就图形而论,DirectX的图形库性能不如OpenGL,OpenGL稳定,可以跨平台使用,DirectX只支持Windows平台,所以OpenGL还是有它的优势!OpenGL ES是OpenG