`

单元测试系列之2:模拟利器Mockito

阅读更多
   引述:程序测试对保障应用程序正确性而言,其重要性怎么样强调都不为过。JUnit是必须事先掌握的测试框架,大多数测试框架和测试工具都在此基础上扩展而来,Spring对测试所提供的帮助类也是在JUnit的基础上进行演化的。直接使用JUnit测试基于Spring的应用存在诸多不便,不可避免地需要将大量的精力用于应付测试夹具准备、测试现场恢复、访问测试数据操作结果等边缘性的工作中。Mockito、Unitils、Dbunit等框架的出现,这些问题有了很好的解决方案,特别是Unitils结合Dbunit对测试DAO层提供了强大的支持,大大提高了编写测试用例的效率和质量。
 
    也许在单元测试框架领域,testNG、JUnit的高下上尚有争论,它们各有自己的拥趸,但是在测试模拟领域,Mockito无疑是翘楚,JMock等只能站后了。

模拟测试概述

   目前支持Java语言的Mock测试工具有EasyMock、JMock、Mockito、MockCreator、Mockrunner、MockMaker等,Mockito是一个针对Java的Mocking框架。它与EasyMock和JMock很相似,是一套通过简单的方法对于指定的接口或类生成 Mock 对象的类库,避免了手工编写Mock对象。但Mockito是通过在执行后校验什么已经被调用,它消除了对期望行为(Expectations)的需要。使用Mockito,在准备阶段只需花费很少的时间,可以使用简洁的API编写出漂亮的测试,可以对具体的类创建Mock对象,并且有“监视”非Mock对象的能力。

   Mockito使用起来简单,学习成本很低,而且具有非常简洁的API,测试代码的可读性很高,因此它十分受欢迎,用户群越来越多,很多开源软件也选择了Mockito。要想了解更多有关Mockito的信息,可以访问其官方网站http://www.mockito.org/。在开始使用Mockito之前,先简单了解一下Stub和Mock的区别。相比Easymock,JMock,编写出来的代码更加容易阅读。无须录制mock方法调用就返回默认值是一个很大优势。目前最新的版本是1.9.0。

   Stub对象用来提供测试时所需要的测试数据,可以对各种交互设置相应的回应。例如我们可以设置方法调用的返回值等。Mockito中 when(…).thenReturn(…) 这样的语法便是设置方法调用的返回值。另外也可以设置方法在何时调用会抛出异常等。

   Mock对象用来验证测试中所依赖对象间的交互是否能够达到预期。Mockito中用 verify(…).methodXxx(…) 语法来验证 methodXxx方法是否按照预期进行了调用。有关 stub和mock的详细论述请见Martin Fowler的文章《Mocks Aren't Stub》,地址为http://martinfowler.com/articles/mocksArentStubs.html。在Mocking框架中所谓的Mock对象实际上是作为上述的Stub和Mock对象同时使用的。因为它既可以设置方法调用返回值,又可以验证方法的调用。

创建Mock对象

   可以对类和接口进行Mock对象的创建,创建的时候可以为Mock对象命名,也可以忽略命名参数。为Mock对象命名的好处就是调试的时候会很方便。比如,我们Mock多个对象,在测试失败的信息中会把有问题的Mock对象打印出来,有了名字我们可以很容易定位和辨认出是哪个Mock对象出现的问题。另外它也有限制,对于final类、匿名类和Java的基本类型是无法进行Mock的。除了用Mock方法来创建模拟对象,如mock(Class<T> classToMock),也可以使用@mock注解定义Mock,下面我们通过实例来介绍一下如何创建一个Mock对象。

import org.junit.Test;
import org.mockito.Mock;
import com.baobaotao.domain.User;
import com.baobaotao.service.UserService;
import com.baobaotao.service.UserServiceImpl;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import org.mockito.MockitoAnnotations;
…
public class MockitoSampleTest{
     
	//① 对接口进行模拟
	UserService mockUserService = mock(UserService.class);
	//② 对类进行模拟
	UserServiceImpl mockServiceImpl = mock(UserServiceImpl.class);
//③ 基于注解模拟类
@Mock
User mockUser;
   
    @Before 
    public void initMocks() {
    //④ 初始化当前测试类所有@Mock注解模拟对象
        MockitoAnnotations.initMocks(this);
    }
     …
 }
…


   在①处和②处,通过Mockito提供的mock()方法创建UserService 用户服务接口、用户服务实现类UserServiceImpl的模拟对象。在③处,通过@Mock注解创建用户User类模拟对象,并需要在测试类初始化方法中,通过MockitoAnnotations.initMocks()方法初始化当前测试类中所有打上@Mock注解的模拟对象。如果没有执行这一步初始化动作,测试时会报模拟对象为空对象异常。

设定Mock对象的期望行为及返回值

   从上文中我们已经知道可以通过when(mock.someMethod()).thenReturn(value)来设定Mock对象的某个方法调用时的返回值,但它也同样有限制条件:对于static和final修饰的方法是无法进行设定的。下面我们通过实例来介绍一下如何调用方法及设定返回值。

import org.junit.Test;
import org.mockito.Mock;
import com.baobaotao.domain.User;
import com.baobaotao.service.UserService;
import com.baobaotao.service.UserServiceImpl;
…
public class MockitoSampleTest {
     …

     //① 模拟接口UserService测试
	@Test
	public void testMockInterface() {
          //①-1 对方法设定返回值	
	when(mockUserService.findUserByUserName("tom")).thenReturn(
				new User("tom", "1234"));
//①-2 对方法设定返回值	
doReturn(true).when(mockServiceImpl).hasMatchUser("tom", "1234");
 //①-3 对void方法进行方法预期设定 
User u = new User("John", "1234");
	doNothing().when(mockUserService).registerUser(u);

//①-4 执行方法调用
		User user = mockUserService.findUserByUserName("tom");
		boolean isMatch = mockUserService.hasMatchUser("tom","1234");
	     mockUserService.registerUser(u);

		assertNotNull(user);
		assertEquals(user.getUserName(), "tom");
		assertEquals(isMatch, true);
	}

	//② 模拟实现类UserServiceImpl测试
@Test
	public void testMockClass() {
          // 对方法设定返回值
		when(mockServiceImpl.findUserByUserName("tom"))
.thenReturn(new User("tom", "1234"));
	doReturn(true).when(mockServiceImpl).hasMatchUser("tom", "1234");

		User user = mockServiceImpl.findUserByUserName("tom");
		boolean isMatch = mockServiceImpl.hasMatchUser("tom","1234");
		assertNotNull(user);
		assertEquals(user.getUserName(), "tom");
		assertEquals(isMatch, true);
	}
	
	//③ 模拟User类测试
@Test
	public void testMockUser() {
		when(mockUser.getUserId()).thenReturn(1);
		when(mockUser.getUserName()).thenReturn("tom");
		assertEquals(mockUser.getUserId(),1);
		assertEquals(mockUser.getUserName(), "tom");
	}
…

 
   在①处,模拟测试接口UserService的findUserByUserName()方法、hasMatchUser()方法及registerUser()方法。在①-1处通过when().thenReturn()语法,模拟方法调用及设置方法的返回值,实例通过模拟调用UserService 用户服务接口的查找用户findUserByUserName()方法,查询用户名为“tom”详细的信息,并设置返回User对象:new User("tom", "1234")。在①-2处通过doReturn (). when ()语法,模拟判断用户hasMatchUser()方法的调用,判断用户名为“tom”及密码为“1234”的用户存在,并设置返回值为:true。在①-3处对void方法进行方法预期设定,如实例中调用注册用户registerUser()方法。设定调用方法及返回值之后,就可以执行接口方法调用验证。在②处和③处,模拟测试用户服务实现类UserServiceImpl,测试的方法与模拟接口一致。

验证交互行为

   Mock对象一旦建立便会自动记录自己的交互行为,所以我们可以有选择地对其交互行为进行验证。在Mockito中验证mock对象交互行为的方法是verify(mock). xxx()。于是用此方法验证了findUserByUserName()方法的调用,因为只调用了一次,所以在verify中我们指定了times参数或atLeastOnce()参数。最后验证返回值是否和预期一样。

import org.junit.Test;
import org.mockito.Mock;
import com.baobaotao.domain.User;
import com.baobaotao.service.UserService;
import com.baobaotao.service.UserServiceImpl;
…
public class MockitoSampleTest {
     …

     //① 模拟接口UserService测试
	@Test
	public void testMockInterface() {
          …
	when(mockUserService.findUserByUserName("tom"))
                         .thenReturn(new User("tom", "1234"));
User user = mockServiceImpl.findUserByUserName("tom");

//①-4 验证返回值
		assertNotNull(user);
		assertEquals(user.getUserName(), "tom");
		assertEquals(isMatch, true);

 //①-5 验证交互行为
verify(mockUserService).findUserByUserName("tom");

//①-6 验证方法至少调用一次
verify(mockUserService, atLeastOnce()).findUserByUserName("tom");
verify(mockUserService, atLeast(1)).findUserByUserName("tom");
	
//①-7 验证方法至多调用一次
verify(mockUserService, atMost(1)).findUserByUserName("tom");
	}
…


   Mockio为我们提供了丰富调用方法次数的验证机制,如被调用了特定次数verify(xxx, times(x))、至少x次verify(xxx, atLeast (x))、最多x次verify(xxx, atMost (x))、从未被调用verify(xxx, never())。在①-6处,验证findUserByUserName()方法至少被调用一次。在①-7处,验证findUserByUserName()方法至多被调用一次。

   这些文章摘自于我的《Spring 4.x企业应用开发实战》的第16章,我将通过连载的方式,陆续在此发出。欢迎大家讨论。
3
1
分享到:
评论
1 楼 sharew 2012-11-15  

谢谢提供这一系列学习资源····

相关推荐

    spring-mock.jar

    Spring Mock是Spring测试工具箱中的利器,它简化了单元测试的复杂性,提高了测试覆盖率和效率。通过熟练掌握模拟对象的创建、配置和验证,开发者可以更加自信地进行代码的编写和维护。在实际项目中,结合Spring Boot...

    jmock-2.4.0-jars.zip

    《jMock 2.4.0:打造高效单元测试的利器》 jMock-2.4.0-jars.zip是一个包含jMock库的压缩文件,它在软件开发领域,特别是单元测试方面扮演着至关重要的角色。jMock是一个开源的Java模拟框架,其目标是简化单元测试...

    powermock 资料_杂

    Mockito作为一款流行的单元测试模拟框架,以其简洁的API和直观的语法,受到了广泛欢迎。与EasyMock和jMock相比,Mockito的一个显著优点是其“验证”模式,即测试后验证函数调用,而非在调用前预设期望行为,从而避免...

    Jmockdta是一款实现模拟JAVA类型或对象的实例化并随机初始化对象的数据的工具框架

    Jmockdata作为单元测试的利器,可以帮助开发者快速构建测试场景,避免因为真实数据的复杂性或者敏感性而带来的问题。通过模拟真实对象,开发者可以控制测试环境,确保测试的可重复性和准确性。 Jmockdata的工作原理...

    springockito-1.0.5.zip

    Mockito是一个流行的Java单元测试框架,允许开发者模拟复杂的依赖关系,而Springockito则将Mockito的功能扩展到了Spring容器中。它使得在测试时可以轻松地模拟bean的行为,隔离被测试对象,避免了因依赖关系导致的...

    20161152163 尚伟辉 1

    总结来说,WinRunner、LoadRunner、JUnit和Rational PurifyPlus分别是功能测试、性能测试、单元测试和代码质量分析的利器,它们在软件开发过程中扮演着至关重要的角色,确保了软件产品的质量和稳定性。通过熟练掌握...

    Spring.3.x企业应用开发实战(完整版).part2

    16.3 模拟利器Mockito 16.3.1 模拟测试概述 16.3.2 创建Mock对象 16.3.3 设定Mock对象的期望行为及返回值 16.3.4 验证交互行为 16.4 测试整合之王Unitils 16.4.1 Unitils概述 16.4.2 集成Spring 16.4.3 集成...

    Spring3.x企业应用开发实战(完整版) part1

    16.3 模拟利器Mockito 16.3.1 模拟测试概述 16.3.2 创建Mock对象 16.3.3 设定Mock对象的期望行为及返回值 16.3.4 验证交互行为 16.4 测试整合之王Unitils 16.4.1 Unitils概述 16.4.2 集成Spring 16.4.3 集成...

    junit4.7.zip

    《深入理解JUnit 4.7:Java开发中的单元测试利器》 JUnit是Java开发者进行单元测试最常用的工具之一,尤其在版本4.7中,它提供了丰富的功能和改进,使得测试更加高效、易于理解和维护。本文将深入探讨JUnit 4.7的...

    基于Java Agent实现的自测,联调Mock利器.zip

    在软件开发过程中,尤其是在自测和联调阶段,Mock工具是必不可少的,它们可以帮助开发者模拟复杂的依赖关系,使测试更加可控。"基于Java Agent实现的自测,联调Mock利器"这个项目可能就是提供了一种利用Java Agent...

    java常用工具大全java常用工具大全

    6. **Mockito**:Mockito是一个模拟框架,用于创建和配置模拟对象,帮助开发者在测试中隔离被测代码,避免外部依赖对测试结果的影响。 7. **SonarQube**:SonarQube是一款静态代码分析工具,用于检测代码中的潜在...

    IDEA插件.zip

    - **Mockito**: 用于创建和管理模拟对象,便于进行隔离测试。 5. **性能优化**: - **YourKit Java Profiler**: 强大的Java性能分析工具,可以帮助开发者定位内存泄漏、CPU瓶颈等问题。 6. **代码辅助与智能提示...

    JdbcTemplate

    可以使用Mockito等工具模拟DataSource,测试业务逻辑而不实际访问数据库。 总的来说,Spring的JdbcTemplate是Java应用中进行数据库操作的优秀选择。它提高了代码的可读性和可维护性,减少了重复的JDBC代码,同时也...

    一个高水平的机器人在java开发框架和工具套件.zip

    JUnit与Mockito等模拟框架结合使用,可以帮助开发者隔离测试代码,更有效地进行测试驱动开发(TDD)。 6. **SonarQube**: SonarQube是一款静态代码分析工具,它可以自动检测代码中的潜在缺陷、漏洞和复杂度,帮助...

    cglib-3.2.4.jar

    2. 测试框架:测试库如Mockito可以使用CGlib来创建被测试对象的模拟(mock)或存根(stub),以便于隔离测试。 3. 数据访问对象(DAO):在数据访问层,CGlib可以帮助创建对数据库操作的代理,实现缓存策略或者事务...

    rxdagger-android:关于 Android 应用架构等的研究、测试等

    同时,Mockito等工具可以帮助你模拟依赖,以便在测试中隔离和控制不同部分的行为。 在实际项目中,RxDagger可以帮助构建松耦合、可扩展的架构,如MVVM(Model-View-ViewModel)或Clean Architecture。在MVVM中,...

    SpringBootTestApp

    在测试方法中,我们可以使用Mockito等库来模拟依赖,确保测试的隔离性。例如,我们可以创建一个mock对象来代替数据库服务,从而避免在测试中真正执行数据库操作。 对于复杂的测试场景,SpringBootTestApp还提供了@...

    idea插件-Squaretest1.5.6

    除此之外,Squaretest还支持Mockito等模拟框架的集成,使得在编写测试时可以更方便地隔离依赖,确保测试的独立性和准确性。同时,它还具备代码重构时的智能感知,确保在重构过程中测试用例不会因为代码变动而失效。 ...

Global site tag (gtag.js) - Google Analytics