`

easymock教程-mock和stub

阅读更多
    作为测试的基本概念,在开发测试中经常遇到mock和stub。之前认为自己对这两个概念已经很明白了,但是当决定要写下来并写清楚以便能让不明白的人也能弄明白,似乎就很有困难。

    试着写下此文,以检验自己是不是真的明白mock和stub。

一. 相同点
   
    先看看两者的相同点吧,非常明确的是,mock和stub都可以用来对系统(或者将粒度放小为模块,单元)进行隔离。

    在测试,尤其是单元测试中,我们通常关注的是主要测试对象的功能和行为,对于主要测试对象涉及到的次要对象尤其是一些依赖,我们仅仅关注主要测试对象和次要测试对象的交互,比如是否调用,何时调用,调用的参数,调用的次数和顺序等,以及返回的结果或发生的异常。但次要对象是如何执行这次调用的具体细节,我们并不关注,因此常见的技巧就是用mock对象或者stub对象来替代真实的次要对象,模拟真实场景来进行对主要测试对象的测试工作。

    因此从实现上看,mock和stub都是通过创建自己的对象来替代次要测试对象,然后按照测试的需要控制这个对象的行为。

二. 不同点

    1. 类实现的方式

        从类的实现方式上看,stub有一个显式的类实现,按照stub类的复用层次可以实现为普通类(被多个测试案例复用),内部类(被同一个测试案例的多个测试方法复用)乃至内部匿名类(只用于当前测试方法)。对于stub的方法也会有具体的实现,哪怕简单到只有一个简单的return语句。

        而mock则不同,mock的实现类通常是有mock的工具包如easymock, jmock来隐式实现,具体mock的方法的行为则通过record方式来指定。

        以mock一个UserService, UserDao为例,最简单的例子,只有一个查询方法:

		public interface UserService {
			User query(String userId);
		}

		public class UserServiceImpl implements UserService {
			private UserDao userDao; 
			public User query(String userId) {
				return userDao.getById(userId);
			}
			//setter for userDao
		}

		public interface UserDao {
			User getById(String userId);
		}


        stub的标准实现,需要自己实现一个类并实现方法:

		public class UserDaoStub implements UserDao {
			public User getById(String id) {
				User user = new User();
				user.set.....
				return user;
			}
		}

	
		@Test
		public void testGetById() {
			UserServiceImpl service = new UserServiceImpl();
			UserDao userDao  = new UserDaoStub();
			service.setUserDao(userDao);

			User user = service.query("1001");
			...
		}


        mock的实现,以easymock为例,只要指定mock的类并record期望的行为,并没有显式的构造新类:

		@Test
		public void testGetById() {
			UserDao dao = Easymock.createMock(UserDao.class);
			User user = new User();
			user.set.....
			Easymock.expect(dao.getById("1001")).andReturn(user);
			Easymock.reply(dao);

			UserServiceImpl service = new UserServiceImpl();
			service.setUserDao(userDao);
			User user = service.query("1001");
			...
			Easymock.verify(dao);
		}


        对比可以看出,mock编写相对简单,只需要关注被使用的函数,所谓"just enough"。stub要复杂一些,需要实现逻辑,即使是不需要关注的方法也至少要给出空实现。

    2. 测试逻辑的可读性

        从上面的代码可以看出,在形式上,mock通常是在测试代码中直接mock类和定义mock方法的行为,测试代码和mock的代码通常是放在一起的,因此测试代码的逻辑也容易从测试案例的代码上看出来。Easymock.expect(dao.getById("1001")).andReturn(user); 直截了当的指明了当前测试案例对UserDao这个依赖的预期: getById需要被调用,调用的参数应该是"1001",调用次数为1(不明确指定调用次数时easymock默认为1)。

        而stub的测试案例的代码中只有简单的UserDao userDao  = new UserDaoStub ();构造语句和service.setUserDao(userDao);设置语句,我们无法直接从测试案例的代码中看出对依赖的预期,只能进入具体的UserServiceImpl类的query()方法,看到具体的实现是调用userDao.getById(userId),这个时候才能明白完整的测试逻辑。因此当测试逻辑复杂,stub数量多并且某些stub需要传入一些标记比如true,false之类的来制定不同的行为时,测试逻辑的可读性就会下降。

    3. 可复用性

        Mock通常很少考虑复用,每个mock对象通过都是遵循"just enough"原则,一般只适用于当前测试方法。因此每个测试方法都必须实现自己的mock逻辑,当然在同一个测试类中还是可以有一些简单的初始化逻辑可以复用。

        stub则通常比较方便复用,尤其是一些通用的stub,比如jdbc连接之类。spring框架就为此提供了大量的stub来方便测试,不过很遗憾的是,它的名字用错了:spring-mock!

    4. 设计和使用

        接着我们从mock和stub的设计和使用上来比较两者,这里需要引入两个概念:interaction-based和state-based。

        具体关于interaction-based和state-based,不再本文阐述,强烈推荐Martin Fowler 的一篇文章,"Mocks Aren't Stubs"。地址为http://martinfowler.com/articles/mocksArentStubs.html(PS:当在google中输入mock stub两个关键字做搜索时,出来结果的第一条就是此文,向Martin Fowler致敬,向google致敬),英文不好的同学,可以参考这里的一份中文翻译:http://www.cnblogs.com/anf/archive/2006/03/27/360248.html。

        总结来说,stub是state-based,关注的是输入和输出。mock是interaction-based,关注的是交互过程。

    5. expectiation/期望

        这个才是mock和stub的最重要的区别:expectiation/期望。

        对于mock来说,exception是重中之重:我们期待方法有没有被调用,期待适当的参数,期待调用的次数,甚至期待多个mock之间的调用顺序。所有的一切期待都是事先准备好,在测试过程中和测试结束后验证是否和预期的一致。

        而对于stub,通常都不会关注exception,就像上面给出的UserDaoStub的例子,没有任何代码来帮助判断这个stub类是否被调用。虽然理论上某些stub实现也可以通过自己编码的方式增加对expectiation的内容,比如增加一个计数器,每次调用+1之类,但是实际上极少这样做。

    6. 总结

        关于mock和stub的不同,在Martin Fowler的"Mocks Aren't Stubs"一文中,有以下结束,我将它列出来作为总结:

                (1) Dummy
                        对象被四处传递,但是从不被真正使用。通常他们只是用来填充参数列表。

                (2) Fake
                        有实际可工作的实现,但是通常有一些缺点导致不适合用于产品(基于内存的数据库就是一个好例子)。

                (3) Stubs
                        在测试过程中产生的调用提供预备好的应答,通常不应答计划之外的任何事。stubs可能记录关于调用的信息,比如 邮件网关的stub 会记录它发送的消息,或者可能仅仅是发送了多少信息。

                (4) Mocks
                        如我们在这里说的那样:预先计划好的对象,带有各种期待,他们组成了一个关于他们期待接受的调用的详细说明。

三. 退化和转化

        在实际的开发测试过程中,我们会发现其实mock和stub的界限有时候很模糊,并没有严格的划分方式,从而造成我们理解上的含糊和困惑。

        主要的原因在于现实使用中,我们经常将mock做不同程度的退化,从而使得mock对象在某些程度上如stub一样工作。以easymock为例,我们可以通过anyObject(), isA(Class)等方式放宽对参数的检测,以atLeastOnce(),anytimes()来放松对调用次数的检测,我们可以使用Easymock.createControl()而不是Easymock.createStrictControl()来放宽对调用顺序的检测(或者调用checkOrder(false)),我们甚至可以通过createNiceControl(), createNiceMock()来创建完全不限制调用方式而且自动返回简单值的mock,这和stub就几乎没有本质区别了。

        目前大多数的mock工具都提供mock退化为stub的支持,比如easyock中,除了上面列出的any***,NiceMock之外,还提供诸如andStubAnswer(),andStubDelegateTo(),andStubReturn(),andStubThrow()和asStub()。

        前面也谈到过stub也是可以通过增加代码来实现一些expectiation的特性,stub理论上也是可以向mock的方向做转化,而从使得两者的界限更加的模糊。

分享到:
评论
5 楼 dotjar 2011-12-02  
不太看得明白,有点费劲,
4 楼 fengjia10 2010-08-31  
   单纯从概念上来看,很难说清楚两者的不同。在分布式计算中,stub代表的是存根,即客户端代理,从测试理论上讲,stub代表的是桩模块,也就是上层模块所调用的模块。而mock的含义大家应该都很清楚哈,Java世界优秀的框架jmock,easymock,jmockit都充斥着以此开头或结尾的方法,其根本是用来隐藏掉你所依赖模块的细节,直接伪造一个(还是代理嗷)。
   不管怎么说,楼主的分享精神还是要赞一个的!
3 楼 skydream 2010-08-31  
wjn811 写道
atLeatOnce() -> atLeastOnce()



感谢指正,已经修改。
2 楼 wjn811 2010-08-31  
atLeatOnce() -> atLeastOnce()

1 楼 skydream 2010-08-30  
很惊讶的发现,获得了11个精华帖投票,但是居然没有一个回复!

无论如何,感谢投票的弟兄们!

相关推荐

    easymock详解教程

    #### 1.2 easymock教程-mock和stub ##### 一、相同点 mock和stub在测试领域中都是为了实现对测试对象(通常是待测代码)与其他系统组件之间交互的模拟,从而确保测试环境的纯净性。它们的主要作用在于: - **隔离...

    easymock教程.pdf

    easymock教程 easymock是一种流行的mocking框架,用于单元测试中模拟依赖对象的行为。下面是关于easymock的详细知识点: ### easymock的基本概念 * Mock对象:是一种虚拟对象,模拟真实对象的行为,用于单元测试...

    easymock教程

    - **相同点**:Mock与Stub都用于实现系统隔离,即在测试过程中,用它们来替代次要对象(即依赖项),使得测试能够集中关注于主要测试对象的功能和行为。 - **不同点**: - **类实现方式**:Stub具有一个显式的类...

    easymock教程[归纳].pdf

    本教程详细介绍了Easymock的各种特性和使用方法,旨在帮助软件开发人员掌握这一工具。 1. **Mock和Stub的概念** - Mock对象是用于代替真实对象的虚拟对象,它在测试中按照预设的规则来响应调用,帮助验证方法是否...

    java easymock

    1. **Mock和Stub**: Mock对象是模拟了特定行为的对象,用于验证方法是否被正确调用,以及它们被调用的方式。Stub则是预先定义了返回值或行为的对象,当被调用时,它们按照预设的规则进行响应,而不进行任何验证。 ...

    单元测试资料

    2. **Mock和Stub的区别**:Mock对象会在测试后验证它被调用的方式,而Stub对象只返回预定义的值,不进行任何验证。 3. **测试DAO**:讲解如何使用Easymock模拟数据库操作,比如预设查询、插入、更新或删除的方法...

    有效使用Mock编写java单元测试

    Java单元测试对于开发人员质量保证至关重要,尤其当...Mock说白了就是打桩(Stub)或则模拟,当你调用一个不好在测试中创建的对象时,Mock框架为你模拟一个和真实对象类似的替身来完成相应的行为。使用如下方式在Mav

    books-powermock-unit-test

    2. **Mocking 和 Stubbing**:了解如何使用 Mock 对象替代实际依赖,以及如何设置 Stub 方法来控制预期的行为。 3. **PowerMock 功能**:学习如何使用 PowerMock 来模拟静态方法、构造函数、final 类和方法、私有...

    junit 学习资料

    9. **假对象(Mocks)与桩(Stubs)**:在单元测试中,有时需要模拟(mock)或替身(stub)外部依赖,JUnit 本身不提供这个功能,但可以结合使用 Mockito 或 EasyMock 这样的库来实现。 10. **测试驱动开发(TDD)*...

    建立测试引擎

    3. **Mocking和Stubbing**:在单元测试中,有时需要模拟(mock)或存根(stub)依赖对象的行为,以隔离测试。Java社区有Mockito、EasyMock和PowerMock等工具来实现这一目的。 4. **集成测试**:对于涉及数据库、...

    junitTesting

    7. **假对象和Mocking**:在JUnit测试中,经常需要模拟(mock)或桩(stub)其他依赖对象,以隔离被测试的代码。JUnit本身并不提供模拟功能,但可以与其他库如Mockito或EasyMock结合使用。 8. **测试驱动开发(TDD...

    java_test

    3. **Mocking和Stubbing**: 在复杂的系统中,有时需要模拟(mock)或存根(stub)依赖对象的行为,以隔离测试并专注于待测试代码的功能。Java社区有多个库可用于此目的,如Mockito和EasyMock。通过mocking,我们可以...

Global site tag (gtag.js) - Google Analytics