2024年2月7日发(作者:)
- 做最棒的软件开发交流社区easymock教程作者: skydreamymock教程,详细的介绍easymock的使用第 1 / 65 页本书由JavaEye提供的电子书DIY功能自动生成于 2010-12-06
目 录1. software test1.1 easymock教程-目录 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .31.2 easymock教程-mock和stub . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .61.3 easymock教程-单元测试中的主要测试对象和依赖 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .121.4 easymock教程-record-replay-verify模型 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .151.5 easymock教程-easymock的典型使用 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .181.6 easymock教程-class mocking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .211.7 easymock教程-mock的限制 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .241.8 easymock教程-strict和nice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .281.9 easymock教程-创建stub对象 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .351.10 easymock教程-放宽调用次数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .401.11 easymock教程-参数匹配 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .421.12 easymock教程-partial class mocking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .461.13 easymock教程-运行时返回值或者异常 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .511.14 easymock教程-改变同一个方法调用的行为 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .551.15 easymock教程-自定义参数匹配器 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .571.16 easymock教程-命名mock对象 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .611.17 easymock教程-使用MockControl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .64第 2 / 65 页
1.1 easymock教程-目录1.1 easymock教程-目录发表时间: 2010-10-14easymock是目前比较流行的java mock 工具,这个教程将比较详细的介绍easymock的使用。主要内容来自easymock的官网介绍和教程,我针对日常使用情况进行了一些筛选和补充,另外增加一些个人的理解和认识,希望能对不熟悉easymock的朋友们有所帮助。如有疏漏,错误或者疑惑,欢迎大家指正和提醒,不胜感激。一. 基本概念这里讲述一些测试相关的基本概念,不仅仅适用于easymock,也同样适用于其他的mock框架如jmock,jmockit等。1)mock 和 stub2)单元测试中的主要测试对象和依赖3)record-replay-verify 模型二. 基础教程1)典型使用2)Class Mocking第 3 / 65 页
1.1 easymock教程-目录3)mock的限制4)strict和nice5)创建stub对象6)放宽调用次数7)参数匹配三. 高级教程1)Partial classmocking2)运行时返回值或者异常3)改变同一个方法调用的行为4)自定义参数匹配器四. 最佳实践1)命名mock对象第 4 / 65 页
1.1 easymock教程-目录2)使用 MocksControl第 5 / 65 页
1.2 easymock教程-mock和stub1.2 easymock教程-mock和stub发表时间: 2010-08-26作为测试的基本概念,在开发测试中经常遇到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为例,最简单的例子,只有一个查询方法:第 6 / 65 页
1.2 easymock教程-mock和stubpublic interface UserService {User query(String userId);}public class UserServiceImpl implements UserService {private UserDao userDao;public User query(String userId) {return d(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();.....return user;}}@Testpublic void testGetById() {UserServiceImpl service = new UserServiceImpl();UserDao userDao = new UserDaoStub();rDao(userDao);User user = ("1001");第 7 / 65 页
1.2 easymock教程-mock和}mock的实现,以easymock为例,只要指定mock的类并record期望的行为,并没有显式的构造新类:@Testpublic void testGetById() {UserDao dao = Mock();User user = new User();.....(d("1001")).andReturn(user);(dao);UserServiceImpl service = new UserServiceImpl();rDao(userDao);User user = ("1001");...(dao);}对比可以看出,mock编写相对简单,只需要关注被使用的函数,所谓"just enough"。stub要复杂一些,需要实现逻辑,即使是不需要关注的方法也至少要给出空实现。2. 测试逻辑的可读性从上面的代码可以看出,在形式上,mock通常是在测试代码中直接mock类和定义mock方法的行为,测试代码和mock的代码通常是放在一起的,因此测试代码的逻辑也容易从测试案例的代码上看出来。(d("1001")).andReturn(user); 直截了当的指明了当前测试案例对UserDao这个依赖的预期: getById需要被调用,调用的参数应该是"1001",调用次数为1(不明确指定调用次数时easymock默认为1)。而stub的测试案例的代码中只有简单的UserDao userDao= new UserDaoStub ();构造语句和第 8 / 65 页
1.2 easymock教程-mock和rDao(userDao);设置语句,我们无法直接从测试案例的代码中看出对依赖的预期,只能进入具体的UserServiceImpl类的query()方法,看到具体的实现是调用d(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"。地址为/articles/(PS:当在google中输入mock stub两个关键字做搜索时,出来结果的第一条就是此文,向Martin Fowler致敬,向google致敬),英文不好的同学,可以参考这里的一份中文翻译:/anf/archive/2006/03/27/。总结来说,stub是state-based,关注的是输入和输出。mock是interaction-based,关注的是交互过程。5. expectiation/期望这个才是mock和stub的最重要的区别:expectiation/期望。对于mock来说,exception是重中之重:我们期待方法有没有被调用,期待适当的参数,期待调用的次数,甚至期待多个mock之间的调用顺序。所有的一切期待都是事先准备好,在测试过程中和测试结束后验证是否和预期的一致。而对于stub,通常都不会关注exception,就像上面给出的UserDaoStub的例子,没有任何代码来帮助判断这个stub类是否被调用。虽然理论上某些stub实现也可以通过自己编码的方式增加对expectiation的内容,第 9 / 65 页
比如增加一个计数器,每次调用+1之类,但是实际上极少这样做。6. 总结1.2 easymock教程-mock和stub关于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()来放松对调用次数的检测,我们可以使用Control()而不是StrictControl()来放宽对调用顺序的检测(或者调用checkOrder(false)),我们甚至可以通过createNiceControl(), createNiceMock()来创建完全不限制调用方式而且自动返回简单值的mock,这和stub就几乎没有本质区别了。目前大多数的mock工具都提供mock退化为stub的支持,比如easyock中,除了上面列出的any***,NiceMock之外,还提供诸如andStubAnswer(),andStubDelegateTo(),andStubReturn(),andStubThrow()和asStub()。第 10 / 65 页
1.2 easymock教程-mock和stub前面也谈到过stub也是可以通过增加代码来实现一些expectiation的特性,stub理论上也是可以向mock的方向做转化,而从使得两者的界限更加的模糊。第 11 / 65 页
1.3 easymock教程-单元测试中的主要测试对象和依赖1.3 easymock教程-单元测试中的主要测试对象和依赖发表时间: 2010-10-14在单元测试中,通常我们都会有一个明确的测试对象,我们测试的主要目的就是为了验证这个类的工作如我们预期。以下面的简单代码为例:publicinterfaceUserService{Userquery(StringuserId);}publicclassUserServiceImplimplementsUserService{privateUserDaouserDao;publicUserquery(StringuserId){d(userId);}publicvoidsetUserDao(UserDao userDao){o=userDao;}}publicinterfaceUserDao{UsergetById(StringuserId);}publicclassUserDaoImplimplementsUserDao{privateDatasourcedataSource;publicUsergetById(Stringid){Useruser=newUser();//executedatabasequery第 12 / 65 页
..returnuser;}1.3 easymock教程-单元测试中的主要测试对象和依赖publicvoidsetDatasource(Datasourcedatasource){urce=datasource;}}这里我们定义有两个interface: UserService 和 UserDao, 并给出了两个实现类UserServiceImpl 和UserDaoImpl。 其中UserServiceImpl依赖到UserDao,通过setter方法可以注入一个UserDao实现。而UserDaoImpl的实现则依赖到Datasource。然后我们来为实现类UserServiceImpl 和 UserDaoImpl编写单元测试:1. UserServiceImplTestpublicclassUserServiceImplTest{@TestpublicvoidtestQuery(){UserexpectedUser=newUser();("1001");(30);e("user-1001");UserDaouserDao=Mock();(d("1001")).andReturn(expectedUser);(userDao);UserServiceImplservice=newUserServiceImpl();rDao(userDao);Useruser=("1001");第 13 / 65 页
ertNotNull(user);assertEquals("1001",());assertEquals(30,());assertEquals("user-1001",e());1.3 easymock教程-单元测试中的主要测试对象和依赖(userDao);}}在这个测试类中,主要测试对象就是UserServiceImpl,对于UserServiceImpl的依赖UserDao,我们采取mock这个UserDao来满足UserServiceImpl的测试需要。2. UserDaoImplTest代码示例就不详细写了,和上面的类似,主要测试对象就是UserDaoImpl, 我们将通过mock Datasource来满足UserDaoImpl对datasource的测试需要。可以从上面的例子中简单的看出,通常单元测试都遵循这样的惯例: AClass的单元测试类命名为AclassTest,主要职责是测试AClass的行为,理所当然的主要测试对象就是AClass。而所有被AClass的依赖则自然而然的成为次要测试对象,通常我们都不关注这些依赖的内部实现,也不会要求在AClass的单元测试案例中对这些依赖的实现进行测试和验证。这也符合单元测试的理念: 我们将类AClass定义为单元,测试这个单元的行为是否如预期。同时也符合UserServiceImpl的实现逻辑:UserServiceImpl依赖到UserDao接口,并不直接依赖到UserDaoImpl,因此在UserServiceImpl的单元测试中,也不应该引入UserDaoImpl这样的真实类,mock框架在这个时候是最适合出场表演的了:我们可以通过mock UserDao来模拟出UserDao的各种行为以便检测UserServiceImpl在这些行为下的处理是否正确: 不同的返回值,错误场景,异常场景。这也是mock框架在单元测试中被广泛使用的原因:还有什么比mock 类更能方便的做到这些?第 14 / 65 页
1.4 easymock教程-record-replay-verify模型1.4 easymock教程-record-replay-verify模型发表时间: 2010-10-15record-replay-verify 模型容许记录mock对象上的操作然后重演并验证这些操作。这是目前mock框架领域最常见的模型,几乎所有的mock框架都是用这个模型,有些是现实使用如easymock,有些是隐式使用如jmockit。以easymock为例,典型的easymock使用案例一般如下, 援引上一章中的例子:publicclassUserServiceImplTest{/***thisisaclassictestcasetouseEasyMock.*/@TestpublicvoidtestQuery(){UserexpectedUser=newUser();("1001");(30);e("user-1001");UserDaouserDao=Mock();(d("1001")).andReturn(expectedUser);(userDao);UserServiceImplservice=newUserServiceImpl();rDao(userDao);Useruser=("1001");assertNotNull(user);assertEquals("1001",());assertEquals(30,());assertEquals("user-1001",e());(userDao);第 15 / 65 页
}}1.4 easymock教程-record-replay-verify模型在这里有两句非常明显的调用语句: (...)和(...)。这两个语句将上述代码分成三个部分,分别对应record-replay-verify 3个阶段1. recordUserexpectedUser=newUser();("1001");(30);e("user-1001");UserDaouserDao=Mock();(d("1001")).andReturn(expectedUser);这里我们开始创建mock对象,并期望这个mock对象的方法被调用,同时给出我们希望这个方法返回的结果。这就是所谓的"记录mock对象上的操作", 同时我们也会看到"expect"这个关键字。总结说,在record阶段,我们需要给出的是我们对mock对象的一系列期望:若干个mock对象被调用,依从我们给定的参数,顺序,次数等,并返回预设好的结果(返回值或者异常).2. replayUserServiceImplservice=newUserServiceImpl();rDao(userDao);Useruser=("1001");第 16 / 65 页
1.4 easymock教程-record-replay-verify模型在replay阶段,我们关注的主要测试对象将被创建,之前在record阶段创建的相关依赖被关联到主要测试对象,然后执行被测试的方法,以模拟真实运行环境下主要测试对象的行为。在测试方法执行过程中,主要测试对象的内部代码被执行,同时和相关的依赖进行交互:以一定的参数调用依赖的方法,获取并处理返回。我们期待这个过程如我们在record阶段设想的交互场景一致,即我们期望在replay阶段所有在record阶段记录的行为都将被完整而准确的重新演绎一遍,从而到达验证主要测试对象行为的目的。3. verifyassertNotNull(user);assertEquals("1001",());assertEquals(30,());assertEquals("user-1001",e());(userDao);在verify阶段,我们将验证测试的结果和交互行为。通常验证分为两部分,如上所示: 一部分是验证结果,即主要测试对象的测试方法返回的结果(对于异常测试场景则是抛出的异常)是否如预期,通常这个验证过程需要我们自行编码实现。另一部分是验证交互行为,典型如依赖是否被调用,调用的参数,顺序和次数,这部分的验证过程通常是由mock框架来自动完成,我们只需要简单调用即可。在easymock的实现中,verify的部分交互行为验证工作,会提前在replay阶段进行:比如未记录的调用,调用的参数等。如果验证失败,则直接结束replay以致整个测试案例。record-replay-verify 模型非常好的满足了大多数测试场景的需要:先指定测试的期望,然后执行测试,再验证期望是否被满足。这个模型简单直接,易于实现,也容易被开发人员理解和接受,因此被各个mock框架广泛使用。第 17 / 65 页
1.5 easymock教程-easymock的典型使用1.5 easymock教程-easymock的典型使用发表时间: 2010-10-15关于easymock的典型使用方式,在easymock的官网文档中,有非常详尽的讲解,文档地址为/EasyMock3_0_,文档的开头一部分内容都是easymock中最基本的使用介绍,虽然是英文,但是非常容易看懂,适用新学者入门。这里只罗列一些简单的常用功能,依然以前面教程中使用到的测试案例为例:publicclassUserServiceImplTestextendsAssert{@TestpublicvoidtestQuery(){UserexpectedUser=newUser();.;UserDaouserDao=Mock();(d("1001")).andReturn(expectedUser);(userDao);UserServiceImplservice=newUserServiceImpl();rDao(userDao);useruser=("1001");assertNotNull(user);assertEquals();//(userDao);}}第 18 / 65 页
1.5 easymock教程-easymock的典型使用这段简短的代码中包含以下easymock的功能:1. 创建mock对象UserDao userDao= Mock();2. 记录mock对象期望的行为(d("1001")).andReturn(expectedUser);这里记录了mock对象的行为:getById()方法被调用,调用次数为1(easymock之中如果没有明确指出调用次数,默认为1),参数为"1001",expectedUser将作为返回值。3. 进入replay阶段(userDao);4. 对mock对象执行验证(userDao);对上面上面的代码稍加改动以展示easymock的其他基本功能:1. 指定期望的调用次数(d("1001")).andReturn(expectedUser).times(3);2. 指定抛出期望的异常(d("1001")).andThrow(new RuntimeException("no user exist"));3. 记录void 方法的行为第 19 / 65 页
1.5 easymock教程-easymock的典型使用(d("1001")) 这样的用法只能使用与mock对象的有返回值的方法,如果mock对象的方法是void,则需要使用expectLastCall():idMethod();LastCall();和(***)一样,同样支持指定调用次数,抛出异常等:LastCall().times(3);LastCall().andThrow(new RuntimeException("some error"));4. 灵活的参数匹配(d(())).andReturn(expectedUser);类似的还有anyInt(),anyObject(), isNull() , same(), startsWith()等诸多实现。具体细节请参考本教程中的"参数匹配"一文。第 20 / 65 页
1.6 easymock教程-class mocking1.6 easymock教程-class mocking发表时间: 2010-10-26前面的例子中,mock的对象都是基于interface,虽然说我们总是强调要面对接口编程,而不要面对实现,但是实际开发中不提取interface而直接使用class的场景非常之多。尤其是一些当前只有一个明确实现而看不到未来扩展的类,是否应该提取interface或者说是否应该现在就提取interface,总是存在争论。这种情况下,我们就会面临主要测试对象依赖到一个具体类而不是interface的情况,easymock中通过classextension 来提供对class mocking的支持。1. class mocking的使用easymock class extension的使用方式和普通的interface mock完全一致,基本上easymock中有的功能easymock class extension都同样提供,而且所有的类名和方法名都保持一致。ClassA impl = Mock();(d("1001")).andReturn(...);(impl);唯一的差异在于,easymock class extension的java package和easymock不同,easymock是ck.*, 而 easymock class extension是xtension.*,典型如ck 对应 ck。另外在发布时,两者是分开发布的, 和 ,需要根据需要分别导入,或者必要时同时导入。2. 3.0新版本和向后兼容我们来回顾一下easymock的历史版本: easymock 1.* 非常久远了,已经没有人在用。2.0版本在2005-12-24发布,基于jdk1.5,之后陆续发布的2.1/2.2/2.3/2.4/2.5等几个版本中,都提供了对应版本的easymock class extension。easymock 3.0 版本是最新版本,2010-05-08 发布,主要改进就是将easymock第 21 / 65 页
1.6 easymock教程-class mockingclass extension的功能合并到easymock中,以后只要使用easymock就可以提供class mocking的功能。当然为了兼容2.*下的旧代码,依然提供了EasyMock 3.0 Class Extension代理到easymock 3.0。3.0版本之后,easymock class extension的class mocking功能已经无缝集成到easymock中,因此代码的编写简洁了很多,强烈建议新用户直接使用3.0版本。对于使用2.*版本的旧有代码,easymock提供了easymock class extension的3.0版本,兼容2.*的代码,底层实现实际是代理给easymock3.0。因此2.*版本easymock class extension的用户可以通过简单的升级easymock class extension到3.0即可平滑升级,之后再逐渐替换掉easymock class extension的代码。3. class mocking的限制class mocking是有一些限制的,1) 不能mock类的 final方法如果final方法被调用,则只能执行原有的正常代码。2) 不能mock类的static 方法。同样如果private方法被调用,只能执行原有的正常代码。3) 不能mock类的一些特殊方法: equals(), toString()和hashCode().原因是easymock在实现是为每个class mock对象提供了内建的以上三个方法。需要强调的是,对于基于interface的mock,这个限制也是同样存在的,即使以上三个方式是interface定义的一部分。在使用时需要避开这种场景,或者组合使用其他的mock 框架比如jmockit来mock private方法和final方法。第 22 / 65 页
1.6 easymock教程-class mocking第 23 / 65 页
1.7 easymock教程-mock的限制1.7 easymock教程-mock的限制发表时间: 2010-11-25easymock并不是万能的,在使用easymock时有一些限制需要注意。(1) Object方法的限制我们都知道java是一个单根继承体系,Object是所有类的基类。在Object类上有几个基本的方法,easymock是不能改变其行为的:equals(), hashCode()和toString()。即对于easymock创建的mock对象,其equals(), hashCode()和toString()三个方法的行为时已经固定了点,不能通过()来指定这三个方法的行为,即使这三个方法是接口定义的一部分。我们来先看一个例子:publicclassBusiness{privateServiceservice;publicvoidexecute(){n("ng()="+ng());n("de()="+de());}publicvoidsetService(Serviceservice){e=service;}}privateinterfaceService{publicStringtoString();publicinthashCode();第 24 / 65 页
}1.7 easymock教程-mock的限制execute()方法将为我们打印出toString()和hashCode()方法的结果。publicclassLimitationTest{privateBusinessbusiness;privateIMocksControlmocksControl;privateServiceservice;@Beforepublicvoidinit(){business=newBusiness();mocksControl=StrictControl();service=Mock();vice(service);}@TestpublicvoidtestDefaultBehavior(){e();}@TestpublicvoidtestCustomizedBehavior(){(ng()).andReturn("CustomizedtoString");(de()).andReturn(100000);();e();();}}第 25 / 65 页
1.7 easymock教程-mock的限制测试案例testDefaultBehavior()将为我们打印出mock对象默认的行为,输出如下:ng() = EasyMock for tionTest$de() = 26208195可见easymock内部已经做好了toString()和hashCode()实现。在测试案例testCustomizedBehavior()中,我们试图通过()来指定toString()和hashCode()的行为,但是运行时遭遇错误:lStateException: no last call on a mock availableat trolForLastCall(:521)at (:499)stomizedBehavior(:51)...从"no last call on a mock available"的描述上看,easymock根本没有把对toString()方法的调用记录(record)下来作为一个对mock对象的调用。因此,在使用mock对象时,请注意equals(), hashCode()和toString()三个方法无法更改其行为。(2) class mock的限制相对于interface mock,class mock下easymock限制更多,除了上面谈到的equals(), hashCode()和toString()三个方法外,还有以下限制:第 26 / 65 页
1.7 easymock教程-mock的限制1. final 方法不能被mock2. private 方法不能对mock(3) 静态方法对于静态方法,easymock也无法mock其行为。由于这个限制,当被测试类中有静态方法调用时,典型如单例方法调用,lookup方式的依赖查找,easymock就会力不从心。从这个角度上,推荐尽量使用IOC 控制反转/ DI依赖注入的方式来实现依赖的获取,而不要使用lookup的主动查找方式。实际开发中,当发现有因为静态方法的限制从而导致easymock无法mock我们期望的行为,造成测试案例"不好写",“写不下去”时,请换个角度思考:为什么要用静态方法?可不可以改成注入?(4) 解决的方法如果由于某些原因必须使用静态方法或者定制final, private方法的行为,则可以考虑搭配其他mock框架来完成功能。以静态方法方法为例,一个典型的使用范例是:使用jmockit来定制静态方法的行为,指定其返回easymock创建的mock对象,然后使用easymock的标准方式定制这个mock对象的行为。第 27 / 65 页
1.8 easymock教程-strict和nice1.8 easymock教程-strict和nice发表时间: 2010-11-19在easymock的使用过程中,当创建mock对象时,我们会遇到 strict mock和nice mock的概念。比如创建mock对象我们通常使用Mock(),但是我们会发现easymock同时提供了两个类似的方法:NiceMock()StrictMock()类似的在创建MocksControl时,除了通常的Control() 外,easymock也同时提供两个类似的方法:NiceControl()StrictControl()我们来看看strict和nice有什么作用。参考easymock的javadoc,我们对比createMock()和createStrictMock():Mock():checking isdisabledby s a mock object that implements the given interface, NiceMock() : Creates a mock object that implements the given interface, orderchecking isenabledby default.发现strict mock方式下默认是开启调用顺序检测的,而普通的mock方式则默认不开启调用顺序检测。再看一下createNiceMock():第 28 / 65 页
1.8 easymock教程-strict和niceCreates a mock object that implements the given interface, order checking is disabled by default, andthe mock object will return 0, null or false for unexpected invocations.和createMock()相同的是默认不开启调用顺序检测,另外有一个非常有用的功能就是对于意料之外的调用将返回0,null 或者false.之所以说有用,是因为在我们的实际开发过程中,有时候会有这样的需求:对于某个mock对象的调用(可以是部分,也可以是全部),我们完全不介意调用细节,包括是否调用和调用顺序,参数,返回值,我们只要求mock对象容许程序可以继续而不是抛出异常报告说 unexpected invocations 。nicemock在这种情况下可以为我们节省大量的工作量,非常方便。我们来看一个简单的实际使用的例子,假设我们有一个Business类,依赖于两个service 接口:先看只调用一个依赖的情况,注意在record阶段2()和1()的顺序和eService1()方法中的实际调用顺序是故意设置为不同的。publicclassBusiness{privateService1service1;privateService2service2;publicvoidexecuteService1(){1();2();}publicvoidexecuteService1And2(){1();2();3();4();}publicvoidsetService1(Service1service1){第 29 / 65 页
e1=service1;}1.8 easymock教程-strict和nicepublicvoidsetService2(Service2service2){e2=service2;}}privateinterfaceService1{publicvoidmethod1();publicvoidmethod2();}privateinterfaceService2{publicvoidmethod3();publicvoidmethod4();}1. 普通mock@TestpublicvoidtestMock(){Businessbusiness=newBusiness();Service1service1=Mock("service1",);vice1(service1);2();LastCall();1();LastCall();(service1);第 30 / 65 页
eService1();(service1);}1.8 easymock教程-strict和nice测试案例可以通过,说明Mock()的确是不检测方法的调用顺序。2. strict mock@TestpublicvoidtestStrictMock(){Businessbusiness=newBusiness();Service1service1=StrictMock("service1",);...}案例失败,错误信息如下ionError:Unexpected method call 1():2(): expected: 1, actual: 0at (:45)at (:73)at al.$1(Unknown Source)at est$eService1(:14)at rictMock(:79)......说明strict mock下,easymock检测到了实际调用时的顺序和预期的不同。3. nick mock@TestpublicvoidtestNiceMock(){Businessbusiness=newBusiness();Service1service1=NiceMock("service1",);第 31 / 65 页
...}1.8 easymock教程-strict和nice测试案例可以通过,而且如果是nick mock的话,record阶段可以简化:@TestpublicvoidtestNiceMockSimplify(){Businessbusiness=newBusiness();Service1service1=NiceMock("service1",);vice1(service1);(service1);eService1();(service1);}这个简化版本的测试案例也是可以通过的。上述的测试案例验证了strict mock和nice mock的基本使用,对于同一个mock对象,strict模式下多个方法之间的调用顺序在record阶段和replay阶段下是需要保持一致的。但是故事并不是到此结束,更有意思的内容在后面:如果出现多个mock对象,那么这些不同mock对象的方法之间,他们的调用顺序是否检测?普通mock和nice mock模式下自然是不会检测顺序,但是strict模式下呢?我们来看需要测试的方法executeService1And2(),这个方法会依次调用service1和service2的方法。使用easymock测试这个方法,注意我们在record阶段依然故意将方法的调用顺序设置为和实际不同。1. 不使用control,直接创建两个strict mock对象@TestpublicvoidtestWithoutControlInWrongOrder(){Businessbusiness=newBusiness();Service1service1=StrictMock("service1",);第 32 / 65 页
vice2service2=StrictMock("service2",);vice1(service1);vice2(service2);1.8 easymock教程-strict和3();LastCall();1();LastCall();(service1,service2);eService1And2();(service1,service2);}这个测试案例,出于意外的,通过了。easymock并没有检测1()和3()这两个方法的调用顺序。2. 使用strict control创建两个strict mock对象@TestpublicvoidtestWithStrictControlInWrongOrder(){Businessbusiness=newBusiness();IMocksControlmocksControl=StrictControl();...}案例失败,错误信息为:ionError:Unexpected method call 1():3(): expected: 1, actual: 0at (:45)at (:73)at al.$1(Unknown Source)at est$eService1And2(:19)thStrictControlInWrongOrder(:218)......第 33 / 65 页
1.8 easymock教程-strict和niceOK,easymock终于检测到1()和3()这两个方法的调用顺序和期望的不一致了。解释一下,StrictMock()方法实际上内部是生成一个新的strict control,然后再创建mock对象。Service1 service1 = StrictMock("service1", );Service2 service2 = StrictMock("service2", );这里实际是创建了两个strict control,而easymock是不会跨control进行顺序检测的。在实际使用过程中,我们会有大量的场景需要检测多个mock之间的调用顺序(按说如果没有特殊要求,一般的测试场景默认都应该如此),这种情况下就必须使用control, 而且必须是同一个strict control才能满足要求。教程后面的最佳实践中有一条就是推荐使用mock control,可以跨mock对象检测方法调用顺序是一个重要原因。第 34 / 65 页
1.9 easymock教程-创建stub对象1.9 easymock教程-创建stub对象发表时间: 2010-11-23前面教程中有个章节讨论到mock和stub的概念差别,一般来说easymock如其名所示,主要是用来做mock用的,但是easymock中也提供有对stub的支持, 主要体现在andStubAnswer(),andStubDelegateTo(),andStubReturn(),andStubThrow()和asStub()等方法的使用上。我们来看一个实际使用的例子:publicclassBusiness{privateServiceservice;privateStubServicestubService;publicvoidexecute1(){if(e()){1();}}publicvoidexecute2(){if(e()){2();}}publicvoidexecute3(){if(e()){1();2();}}第 35 / 65 页
licvoidsetStubService(StubServicestubService){rvice=stubService;}1.9 easymock教程-创建stub对象publicvoidsetService(Serviceservice){e=service;}}privateinterfaceService{publicbooleanexecute();}privateinterfaceStubService{publicStringmethod1();publicStringmethod2();}这里的Business类依赖到Service和StubService,execute1() / execute2() / execute3() 是我们需要测试的三个方法,相同点都是必须调用e(),不同点在于其后对stubService的调用各不相同。而我们假设在这里我们只关心Business类对Service的调用是否如预期,不关心对于StubService的调用,只要程序可以继续运行就可以了。一个正统的做法是手工写一个StubService的stub 实现,例如:privateclassStubServiceImpl{publicStringmethod1(){return"";}publicStringmethod2(){return"";}第 36 / 65 页
}1.9 easymock教程-创建stub对象但是如果这个接口复杂方法众多,则这个stub类不得不实现所有的其他方法,即使完全用不到,因为java的语法限制。可以这样使用easymock来实现stub:publicclassBusinessTest{privateBusinessbusiness;privateIMocksControlmocksControl;privateServiceservice;@Beforepublicvoidinit(){business=newBusiness();bService(prepareStubService());mocksControl=StrictControl();service=Mock("service",);vice(service);}privateStubServiceprepareStubService(){StubServiceservice=Mock("stubService",);1();LastCall().andStubReturn("");2();LastCall().andStubReturn("");第 37 / 65 页
(service);returnservice;}1.9 easymock教程-创建stub对象@TestpublicvoidtestExecute1(){(e()).andReturn(true);();e1();();}@TestpublicvoidtestExecute2(){(e()).andReturn(false);();e2();();}@TestpublicvoidtestExecute3(){(e()).andReturn(true);();e3();();}}在方法prepareStubService()中,我们通过easymock创建了一个mock对象,然后和普通mock对象一样记录了期望的行为。不同的是用andStubReturn()替代了andReturn().然后我们直接调用(service),注意在这个测试案例中,我们另外创建了一个mocksControl并通过这个mocksControl创建了我们关注的Service接口的mock对象,它的record/replay/和verify()是和StubService完全分离的。这样做的好处是在execute1() / execute2() / execute3()的测试案例中,我们完全不第 38 / 65 页
1.9 easymock教程-创建stub对象必额外关心这个stub,所有的事情在init()函数中就已经准备好了。这样做的好处显而易见,execute1() /execute2() / execute3()的测试案例中,代码和测试逻辑都简单了。最后总结,在适当的时候使用easymock来创建stub对象,对于简化测试还是能有所帮助的。第 39 / 65 页
1.10 easymock教程-放宽调用次数1.10 easymock教程-放宽调用次数发表时间: 2010-11-29对于mock对象上的mock方法的调用,easymock支持指定次数,默认为1,例如(1()).andReturn(...);这里没有显式的指定调用次数,因此效果等同于(1()).andReturn(...).once();同时easymock提供了其他的方法,用于指定具体调用次数或者放宽调用次数检验。1. once()如果明确调用次数为1,则可以使用这个方法显式指定,也可以省略,easymock默认为1。2. atLeastOnce()指定调用为1次或者多次,即 count >= 1.3. anyTimes()容许调用次数为任意次,即 count >= 0.4. times(int count)直接指定调用次数5. times(int min, int max)这个方法比较灵活,可以指定最小次数和最大次数。第 40 / 65 页
1.10 easymock教程-放宽调用次数其他的方法都可以视为这个方法的变体,比如once()等价于times(1,1)atLeastOnce()等价于times(1,_VALUE)anyTimes()等价于times(0,_VALUE)times(int count)等价于times(count,count)实际使用中根据具体要求可以灵活选用上述方法来指定我们期望的调用数次。第 41 / 65 页
1.11 easymock教程-参数匹配1.11 easymock教程-参数匹配发表时间: 2010-11-29easymock中提供了非常多的方法来实现参数匹配,基本能满足一般参数匹配的要求。我们来具体看一下到底有哪些方法:(1) 基于基本类型的比较1. eq(X value)方法, X 可以是boolean,byte,char, double,float,int,long,short,T有多个重载方法,支持基本类型如boolean, byte,char, double,float,int, long,short,后面会介绍它也支持Object比较。这个eq()方法的用法直接了当,基本数值直接比较数值,对于非整型的double和float,由于存在精度的问题,因此增加了以下两个方法来指定比较精度。eq(doublevalue,doubledelta)eq(floatvalue,floatdelta)2. aryEq(X[] values) X 可以是boolean,byte,char, double,float,int,long,short,T这个是eq(X value)方法的数组版本,要求比较的两个数组拥有相同的长度,然后每个元素都"相同",即都可以满足eq(X value)方法。注意到double和float并没有像eq(X value)方法那样提供可以设置精度的重载版本,不知道在数组比较时如何去设置容许精度。3. gt(X value), lt(X value), X 可以是byte,double,float,int,long,short这两个方法用于参数的大小匹配,适用于数值型的基本类型如byte,double,float,int,long,short。4. geq(X value), leq(X value)第 42 / 65 页
1.11 easymock教程-参数匹配类似gt()和lt(),无非是将">"改为">=", "<"改为"<="。5. anyX(), X可以是Boolean, Byte, Char, Double, Float, Int, Long, Short这是一个宽松的匹配方法,任何数值都被视为匹配OK。这个方法在我们不介意参数值时特别有用。(2) 基于对象的比较1. eq(T value)方法和基本类型类似,不过对于Object,是通过调用equals()方法来进行比较。2. same(T value) 方法和eq()不同,same()是通过比较对象引用来进行比较的。类似java代码中, (b)和a == b的差别。3. anyObject() 和 anyObject(Class
1.11 easymock教程-参数匹配(3) 逻辑计算easymock支持在参数匹配时进行一些简单的逻辑计算, 如and(), or (), not()。not()容易理解,取反而已。or()也容易理解,两个匹配方法匹配一个即可。而and()匹配方法通常用于设置取值区间,典型如and(gt(0), lt(5))) 的写法可以设置期望值大于0而小于5,即(0,5)区间。此外在参数匹配中,有几个特殊角色,享受的待遇与众不同,easymock为它们提供了专有方法。1. Comparable对于实现了Comparable接口的对象,easymock提供了一系列的专用方法来处理,包括eq, gt, lt, geq,leq:cmpEq(Comparable
1.11 easymock教程-参数匹配3. null对于Object匹配,很常见的一个场景就是输入的参数为null,easymock中提供isNull() 和 notNull() 两个方法来完成对null值的匹配。开发中,经常会遇到下面这种场景,期望输入的参数满足isA()或者容许为null。而直接使用isA(),是不能支持null的,即如果参数为null时isA()会报不匹配。这个不是easymock的bug,而是刻意而为,解决的方法是使用 or(isA(...), isNull(...))或者anyObject()。e((ClassA)((),()));e(ect());第 45 / 65 页
1.12 easymock教程-partial class mocking1.12 easymock教程-partial class mocking发表时间: 2010-11-30easymock中提供对于类的mock功能,我们可以方便的mock这个类的某些方法,指定预期的行为以便测试这个类的调用者。这种场景下被mock的类在测试案例中扮演的是次要测试对象或者说依赖的角色,主要测试对象是这个mock类的调用者。但是有时候我们需要将这个测试类作为主要测试对象,我们希望这个类中的部分(通常是大部分)方法保持原有的正常行为,只有个别方法被我们mock掉以便测试。1. 使用方法我们先来看看这个partial class mocking 是如何工作的:publicclassService{publicvoidexecute(){actualMethod();needMockMethod();}voidactualMethod(){n("callactualMethod()");}publicvoidneedMockMethod(){n("callneedMockMethod()");}}我们给出了一个非常简单的类,我们将要测试execute()方法,期望能测试到actualMethod()这个方法的正常行为,然后需要mock掉needMockMethod().publicclassPartialClassMockTestextendsAssert{第 46 / 65 页
@TestpublicvoidtestPartialMock(){1.12 easymock教程-partial class mockingServiceservice=MockBuilder().addMockedMethod("needMockMethod").createMock();ckMethod();LastCall();(service);e();(service);}}上面的测试案例运行通过,输出为"call actualMethod()",没有"call needMockMethod()",说明我们设置的mock生效了。我们创建的mock类的确是只有部分我们制定的方法是mock的,其他都是正常行为。再来看看为什么我们要需要partial class mocking 这个功能?为什么需要mock掉其中的一个方法?我们来看看下面这个更加真实的例子:publicclassService{publicStringexecute2(){returngetConfiguration();}publicStringgetConfiguration(){rname();}}publicclassConfiguration{publicstaticStringgetUsername(){//ignorethecodetogetconfigurationfromfileordatabasereturn"username";第 47 / 65 页
}}1.12 easymock教程-partial class mocking这里例子中,需要测试的 execute2()方法需要调用getConfiguration()方法,而getConfiguration()方法则调用了Configuration的静态方法来获取配置信息。我们假设读取配置的代码比较复杂不能直接在单元测试环境下运行,因此通过情况下这里的execute2()方法就会因为这个getConfiguration()而造成无法测试。因此我们可以考虑通过partial class mocking的功能来mock掉getConfiguration()方法从而使得我们的测试案例可以覆盖到execute2()方法@TestpublicvoidtestStaticMethod(){Serviceservice=MockBuilder().addMockedMethod("getConfiguration").createMock();(figuration()).andReturn("abc");(service);assertEquals("abc",e2());(service);}这个测试案例可以正常通过,我们通过partial class mocking成功的避开了getConfiguration()这个绊脚石。当然这里的实例代码本身就有点问题,应该采用DI的方法将configuration注入进来,而不是在内部通过静态方法来获取。因此一个建议是在使用partial class mocking功能前,先看看是不是可以通过重构来显改进测试类。只有当我们有足够充分的不得已的理由时,才使用partial class mocking这种变通(或者说取巧)的方式来解决问题。2. 限制上面两个例子中,我们仔细看看会发现,被mock的方法都是public的。我们试着将方法修改为protected和default,partial class mocking依然生效。但是修改为private之后,则抛出异常:第 48 / 65 页
1.12 easymock教程-partial class lArgumentException: Method not found (or private): needMockMethodat kedMethod(:75)rtialMock(:52)或者将mock的方法继续保持public,但是加上final,则抛出以下异常:lStateException: no last call on a mock availableat trolForLastCall(:521)at LastCall(:512)rtialMock(:54)我们回到之前的章节,class mocking里面讲述了class mocking的一些限制:private方法和final方法是不能mock的。partial class mocking下这些限制依然存在。因此,为了开启partial class mocking,我们不得不稍微破坏一下类的封装原则,对于原本应该是private的方法,修改为protected或者default。不得不再次申明,partial class mocking不是一个足够好的解决方案,它只适合在不得已的情况下使用,不要太依赖这个特性。重构代码改善代码才是王道。3. 疑问另外class mocking中还讲到,对于类的equals(), toString()和hashCode()这三个方法,class mocking下是easymock为这三个方法内建了easymock的实现,因此也不能mock。而partial class mocking,这三个方法同样不能mock,但是easymock不再为它们内建实现,而是使用它们正常的功能。关于这点还是有一点疑问,我在easymock的官方文档中看到以下描述Remark: EasyMock provides a default behavior for Object's methods (equals, hashCode, toString).However, for a partial mock,if these methods are not mocked explicitly, they will have their normalbehavior instead of EasyMock default's one.第 49 / 65 页
本文发布于:2024-02-07 13:29:34,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170728377465090.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |