`

我摸、我摸、我摸摸摸

阅读更多

 

      如果把UT比作一个长满仙人掌,那么类依赖、外部环境就可以看做仙人掌上的刺了,为了让coder们在摸这个仙人掌时,不会被这些烦人的刺给扎到手,现在在Java行业里,市面上出现了许多的mock服务,这里简称“摸客”,各显其能,就是为了将这些刺给剃光,让coder们摸起来顺手舒适。真正的单元测试运行起来通常都是非常迅速的,因为它不需要依赖于数据库、服务器等等运行设施,这样才能大大提高coder的生产力,而结合这些摸客和JUnit,现在UT就能够真真正正达到这个目标了。Easymock,就是其中的一个摸客,正如其名,这个“一级”摸客(谐音,easy让我想到了中文的一级,Easymock的作者估计挺熟悉中文,给起了个这么个好名字),在对接口依赖、外部环境打桩方面非常easy,可以大大提高coder编写UT代码时的效率。

      Easymock使用起来很简单,用Easymock写的UT代码,通常由录制、回放(执行)、验收这三个部分来构成:
1、录制:构造待测试方法中依赖的接口对象(后续统称为mock对象),然后指定mock对象在待测试代码中将有哪些方法会被调用,以及调用的结果;
2、回放(replay):执行待测试方法;
3、验收(verify):检查各个mock对象(摸客对象)被录制的方法最终是否被正确调用了,以及待测试代码根据这些mock对象的执行结果是否做了正确的事。
一般我们都是先code代码,然后写UT代码进行测试。如果使用Easymock,整个code、测试的过程就是:code、录制、回放、验收,这好比拍电影,先写剧本(待测试方法),接着按照剧本准备一切(包括道具、演员等),然后开拍,最好导演进行验收,如果一切是按着剧本演的,那么就可以GO,否则得NG重来。

      下面是某女按照其爱情观编写出的一个剧本(类),描述了男孩和女孩之间的爱情故事,显然,这个故事需要一男一女两个角色(依赖Boy和Girl接口,这里Boy和Girl都是接口类,Boy可以是有钱的Boy,也可以是个穷光蛋,Girl可以是个美女,也可以是只暴龙)。现在我们试着用“一级摸客”的拍摄流程,来导演这个剧本。

 

class Love
{
	private Boy boy;

	private Girl girl;

	public Love(Boy boy, Girl girl) {
		this.boy = boy;
		this.girl = girl;
	}

	public Object love() 
	{
		if(boy.有房() && boy.有车()) {
			return girl.嫁给(boy); 
		} else if (girl.愿意等()) {
			while(boy.活着() && girl.活着()) {
				for(int day=1; day <= 365; day++) {
					if (day == 节日.情人节) {
						if (boy.givegirl("玫瑰")) {
							girl.感情递增(); 
						} else {
							girl.感情递减(); 
						}
					}
					if(day == girl.生日()) {
						if(boy.givegirl("玫瑰")) {
							girl.感情递增(); 
						} else {
							girl.感情递减(); 
						}
					}
					boy.拼命赚钱();
				}
				boy.年龄递增();
				girl.年龄递增();
				if(boy.有房() && boy.有车()) {
					return girl.嫁给(boy); 
				} else if(!(boy.年收入() > 100000 && girl.感情() > 0)) {
					return girl.离开(boy);
				}
			}
			return girl.离开(boy);
		} else {
			return girl.离开(boy);
		}
	}
}

 首先,在我们的TestCase中引入“一级摸客”的包:

import static org.easymock.EasyMock.*;

接着分别为我们的拍摄需要的演员Boy和Girl创建一个摸客对象(mock对象):

Boy mockBoy = createMock(Boy.class);
Girl mockGirl = createMock(Girl.class);

createMock是EasyMock用于创建摸客对象的方法,入参是你想要创建的桩的类class。现在我们开始开拍,首先让我们的演员——男女摸客不做任何事情,直接回放:

replay(mockBoy);
replay(mockGirl);
love.love();

在测试我们待测试方法之前,所有将要用到的摸客对象都必须通过replay方法切换到回放状态,否则调用摸客对象上的方法,EasyMock是毫不留情的抱怨的。
完整的代码大概如下:

public void testLove() {
	Boy mockBoy = createMock(Boy.class);
	Girl mockGirl = createMock(Girl.class);
	Love love = new Love(mockBoy, mockGirl);
	replay(mockBoy);
	replay(mockGirl);
	love.love();
}

第一个场景的所有事项都已经搞定了,让我们在跑跑我们的testcase,结果导演大喊一声“咔”:

java.lang.AssertionError: 
  Unexpected method call 有房():
	at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:32)
	at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:61)
	at $Proxy4.有房(Unknown Source)

因为这个love脚本中,它是期望我们的男主角要回应【有房】和【有车】这两个方法调用的,结果我们的男摸客却非常木呐,什么都没有做,所以肯定会被我们的大导演NG掉的。
      现在假设我们的男主角是个有车、有房的金龟婿,看看我们的女主角是否会嫁给她,按照我们的脚本是会的:

public void testLove() {
	Boy mockBoy = createMock(Boy.class);
	Girl mockGirl = createMock(Girl.class);
	Love love = new Love(mockBoy, mockGirl);
	expect(mockBoy.有房()).andReturn(true);
	expect(mockBoy.有车()).andReturn(true);
	expect(mockGirl.嫁给(mockBoy)).andReturn("结婚");
	replay(mockBoy);
	replay(mockGirl);
	assertEquals("结婚", love.love());
	verify(mockBoy);
	verify(mockGirl);
}

      这时我们的导演满意的笑了——junit变绿了。让我们来看看这个场景中,我们都做了些什么才使得我们的大导演满意了。首先,我们分别调用了男摸客对象的有房、有车接口,假如你认为待测试方法中会调用mock对象的某个方法,那么在replay之前,你需要调用下mock对象的相应方法(就像上面的mockBoy.有房()一样),然后待测试方法在执行过程中如果没有执行你指定的方法,那么测试就通不过。一般会得到类似下面的信息(这里假设把love方法中的boy.有房()语句删除掉):

java.lang.AssertionError: 
  Expectation failure on verify:
    有房(): expected: 1, actual: 0
	at org.easymock.internal.MocksControl.verify(MocksControl.java:101)

假如你的方法是有返回值的,那么你就需要用expect和andReturn来指定摸客方法的返回值,如上面的:

expect(mockBoy.有房()).andReturn(true);

如果你希望你的摸客方法抛出某个异常,则可以用andThrow来代替andReturn,方法参数是你希望抛出的异常类对象:

expect(mockBoy.有房()).andThrow(new RuntimeException());

最后,在测试方法执行过后,我们除了用assertTrue、assertEquals等方法来验证方法的执行结果外,别忘了要verify我们的摸客对象,否则某些我们指定的摸客对象的方法调用在被测试方法中如果没有被执行的话,这些就会成为漏网之鱼了,就上面举的例子一样,如果没有verify(mockBoy)的话,那么把love方法中的body.有房()删掉后,测试代码依然能够跑通,但这并不是我们想要的。
      OK,大部分女孩都会愿意嫁给个有钱的主,如果男主角是个没房、没车的主呢?那就要看女主角愿不愿意等了,假如她认为他是个“潜力股”,她愿意等他发达,那来看看故事的发展过程:

public void testGirlWantToWait() {
	Boy mockBoy = createMock(Boy.class);
	Girl mockGirl = createMock(Girl.class);
	Love love = new Love(mockBoy, mockGirl);
	expect(mockBoy.有车()).andReturn(false);
	expect(mockGirl.愿意等()).andReturn(true);
	expect(mockBoy.活着()).andReturn(true).anyTimes();
	expect(mockGirl.活着()).andReturn(true).anyTimes();
	expect(mockGirl.生日()).andReturn(100).anyTimes();
	expect(mockBoy.givegirl("玫瑰")).andReturn(true).times(4);
	mockGirl.感情递增();
	expectLastCall().times(4);
	mockBoy.拼命赚钱();
	expectLastCall().anyTimes();
	mockBoy.年龄递增();
	expectLastCall().times(2);
	mockGirl.年龄递增();
	expectLastCall().times(2);
	expect(mockBoy.有房()).andReturn(false).times(2);
	expect(mockBoy.年收入()).andReturn(100001);
	expect(mockGirl.感情()).andReturn(1);
	expect(mockBoy.年收入()).andReturn(100000);
	expect(mockGirl.离开(mockBoy)).andReturn("分手");
	replay(mockBoy);
	replay(mockGirl);
	assertEquals("分手", love.love());
	verify(mockBoy);
	verify(mockGirl);
}

这里,我们用了一个新的方法expectLastCall()来表示上一个mock对象的方法调用,另外还用了两个新的方法:anyTime()和times(***),用于表示上一个mock对象的方法调用将会被执行的次数,anyTime()表示执行任意次,times(***)表示执行指定次数。上面我们假定了男主角没有车且女主角愿意等,然后两人继续发展了两年:

mockBoy.年龄递增();
expectLastCall().times(2);

在这两年内男主角为了讨女主角欢心,每逢重大节日都送女主角花,但是仅仅第二年的年收入比女主角预期的少了一块,就被女主角“飞掉”了(真够现实的^_^):

expect(mockBoy.年收入()).andReturn(100000);
expect(mockGirl.离开(mockBoy)).andReturn("分手");

现在跑跑我们的测试,看看这种预想按我们的love剧本演下去,其结果是不是和我们预想的一样,结果我们的junit又再次亮起了绿灯,证明我们的剧本写的没有问题。
      EasyMock在判断某个预期的方法调用最终是否被执行了时,对于方法上的参数对象的匹配缺省是通过对象的equals方法来比较预期方法调用是指定的参数与实际方法调用时指定的参数是否匹配的。这种缺省方式可能不能满足我们的需求,例如有一个接口,需要一个字符串数组做为参数:

String[] params = new String[]{“param1”, “param2”};
expect(mock.parse(params)).andReturn(2);

这里我们期望的是,接口在实际被调用时传入的参数是一个长度为2,第一个元素为param1,第二个元素为param2就认为是正确的,但由于EasyMock是用equals进行参数匹配的,而数组的equals方法是按照对象地址进行匹配的,这样即使数组内容相同,但由于两个数组对象本身不是同一个对象,这时EasyMock就开始报错,这不是我们想要的。为了解决这个问题,EasyMock提供了其它一些进行参数匹配的方法,如aryEq:

String[] params = new String[]{“param1”, “param2”};
expect(mock.parse(aryEq(params))).andReturn(2);

aryEq按照数组元素进行匹配。。其它常用的参数匹配器如下:

eq(X value):当实际值和预期值相同时匹配。适用于所有的简单类型和对象;
anyBoolean()、anyByte()、anyChar()、anyDouble()、anyFloat()、anyInt()、anyLong()、anyObject():匹配任意值,假如你只想测试下你的mock对象是否被调用,而不管调用时使用的参数,就可以用这些方法;
aryEq(X value):两个数组的内容相同时匹配(当Arrays.equals()返回true时匹配),适用于任意基本类型与对象的数组;
isA(Class clazz):当实际值为指定class的对象时匹配(也可以是指定class子类的对象),适用于对象;
isNull():当实际值为null时匹配,适用于对象;
notNull():当实际值不为nll时匹配,适用于对象;
same(X value):当实际值与预期值为同一个对象时匹配,即=返回true时匹配,适用于对象;
此外还有适用于数值比较的lt、gt、leq、geq;适用于字符串比较的startsWith、contains、endsWith、matches、find;表示逻辑关系的:and、or、not等。
虽然EasyMock已经内建了那么多匹配器,但我们有时会遇到需要自定义参数匹配器的情况。通过下面两个步骤来完成自定义参数匹配器的实现:
1.    定义新的参数匹配器,实现org.easymock.IArgumentMatcher接口;
2.    声明静态方法用于向EasyMock报告自定义参数匹配器;
假设我们需要一个能够对List内容进行匹配的匹配器(只是举例,其实根本不需要用到这种匹配器),首先我们来实现IArgumentMatcher:

import java.util.List;
import org.easymock.IArgumentMatcher;

public class ListArgumentMatcher implements IArgumentMatcher {
	private List expected;

	public ListArgumentMatcher(List expected) {
		this.expected = expected;
	}

	public void appendTo(StringBuffer buffer) {
		buffer.append("eqCollection(");
		buffer.append(expected.getClass().getName());
		buffer.append(" with values:");
		buffer.append(expected);
		buffer.append(")");
	}

	public boolean matches(Object actual) {
		if (actual instanceof List) {
			List actualList = (List) actual;
			if (expected.size() == actualList.size()) {
				for (int i = 0; i < expected.size(); i++) {
					if ((expected.get(i) == null && actualList.get(i) != null)
							|| !expected.get(i).equals(actualList.get(i))) {
						return false;
					}
				}
				return true;
			}
			return false;
		}
		return false;
	}
}

 如上所示,IArgumentMatcher接口需要实现两个方法:appendTo用于添加表示该匹配器的字符串到指定的StringBuffer中;matches用于实现对象的匹配。
    然后在我们的TestCase中添加eqList的静态方法用来报告这个匹配器:

public static <T extends List> T eqList(T in) {
	reportMatcher(new ListArgumentMatcher(in));
	return null;
}

 这里我们调用了EasyMock的reportMatcher方法报告了我们的参数匹配器,然后返回一个值,一般返回null、false或0就可以了。
      在上面的例子中,所有的Mock对象都是针对于接口来创建的,这是EasyMock的基本能力,对于新开发的系统来说,这应该足够了,因为我们可以约束新代码需要基于接口进行编程,但现实中也存在既有代码没有按接口编程来实现,这时怎么办呢?感谢EasyMock,它提供了一个扩展包,用于基于类创建mock对象,针对类创建mock对象的方式与针对接口创建mock对象的方式完全一样,惟一不同是这时需要引入:

import static org.easymock.classextension.EasyMock.*;

包。例如AccountDao是一个普通类,没有实现任何接口,那么创建AccountDao的mock对象和上面创建Boy接口的mock对象完全一样:

AccountDao mockAccountDao = createMock(AccountDao.class);

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2
3
分享到:
评论
3 楼 Hotpepper 2009-03-23  
写这个之前确实看过他的文章,不过那也是很久之前的事了,不怕被笑话,那时只是对EasyMock有一点点的了解,没有用过,写了将近3年的代码了,参加那么多项目了,到现在没有一个项目真真正正地进行UT的,即使写了,各个版本的UT都没有继承性,且UT代码几乎就和IT代码一样了,因为没有用过mock工具。最近的一个项目,准备真真正正地将UT开展起来,选择了EasyMock,觉得非常好用,另外最近非常喜欢将自己学的东西,写出来,一方面加深理解,另一方面Share,虽然很多是比较肤浅的、很多人都懂的,但我也想这样一直进行下去,学一点,写一点,感觉挺快乐的。在写这个时,题目标题多多少少有点“拿来主义”,千万别见怪^_^
2 楼 xmx0632 2009-03-22  
貌似跟ajoo的这个俺摸,俺摸,俺默默摸http://www.iteye.com/topic/156764标题取的都差不多嘛
一级摸客的叫法都一样
1 楼 gurudk 2009-03-13  
摸客,这名字不错!

相关推荐

    辅助软件 点阵取摸软件 辅助软件 点阵取摸软件

    辅助软件 点阵取摸软件辅助软件 点阵取摸软件辅助软件 点阵取摸软件辅助软件 点阵取摸软件辅助软件 点阵取摸软件辅助软件 点阵取摸软件辅助软件 点阵取摸软件辅助软件 点阵取摸软件辅助软件 点阵取摸软件辅助软件 ...

    立体图形建模摸摸摸摸密密麻麻

    立体图形建模摸摸摸摸密密麻麻

    摸摸瓦力欧制造汉化.SAV

    摸摸瓦力欧制造汉化.SAV

    幼儿园大班心理健康活动课件 不要摸我.ppt

    幼儿园大班心理健康活动课件 不要摸我

    触摸布板建议

    1. 触摸按键板尽量单独布板,这样可以降低干扰。 2. 触摸芯片的电源要求独立供电,不要和其它器件共用同一组电源, 要求稳压,尽量降低 纹波(小于110MV 为佳);...摸按键铜皮的背面不要走线。以免干扰。

    小猫摸鱼.doc

    妈妈给每位宝宝一个小食品袋,内装有 5 以内数量标小食品,请小猫把眼睛闭起来,摸摸袋中有多少多食品。 〔2〕一起分享食品。 小猫摸鱼运动的实施细节中,涵盖了多种感官感受和数学征询的内容,为小猫提供了一个...

    电容触摸按键方案

    概述 BS81x 系列芯片具有 2~16 个触摸按键, 可用来检测外部触摸按键上人手的触摸动... 此系列的触摸芯片具有自动校准功能,低 待机电流,抗电压波动等特性,为各种触 摸按键的应用提供了一种简单而又有效的 实现方法。

    上班摸鱼,上班摸鱼,上班摸鱼

    标题 "上班摸鱼,上班摸鱼,上班摸鱼" 提示了这是一个关于在工作时间进行非正式活动,可能涉及如何巧妙地在工作中隐藏个人行为的主题。描述 "老板来人不要怕,快速隐藏程序" 明确指出这个话题是关于如何在领导或同事...

    一个可以让你在所有网站看小说的浏览器摸鱼插件

    Step 2: 使用《摸鱼王》阅读小说 安装完插件后,你可以在任何网页上激活《摸鱼王》。只需点击浏览器角落的插件图标,输入你想阅读的小说名称或选择你喜欢的小说网站。插件会在当前页面打开一个隐蔽的小窗口,完美...

    摸头杀,忍の摸头之术html网页源码源码资源下载整理.zip

    【摸头杀,忍の摸头之术html网页源码】是一个HTML网页设计项目,它以独特的主题——“摸头杀”为概念,为用户提供了一种互动体验。在这个项目中,我们可以深入学习HTML(HyperText Markup Language)的基础知识及其...

    股票摸鱼神器程序员股票摸鱼神器.zip

    股票摸鱼神器是一款专为程序员设计的股票监测和分析工具,旨在帮助程序员在忙碌的工作间隙高效地关注股市动态,实现工作与投资的巧妙结合。这款神器集成了多种实用功能,如实时行情显示、自选股管理、技术指标分析等...

    自己摸鱼专用,摸鱼摸鱼魔域

    自己摸鱼专用,摸鱼摸鱼魔域

    经典TG PPT摸版

    自己收藏的PPT精品摸版,其中包括2款TG摸版望大家喜欢

    摸一摸.doc

    【摸一摸.doc】是一个关于幼儿教育的科学活动设计,主要目标是通过触觉感知来训练幼儿对不同材质和形状物品的理解。这个活动的核心在于利用触觉感官来探索和识别物体的特性,如软硬、光滑与粗糙、冷热等,并且通过一...

    摸头GIF在线生成HTML源码

    找一个图片上传网站可以自动生成摸头动图,源码上传虚拟空间或者服务器,支持上传二级目录访问

    上班摸鱼打卡模拟器微信小程序源码.zip

    俗话说,摸鱼摸的好,上班没烦恼,毕竟谁能拒绝带薪拉屎呢(手动狗头) 这是一个云开发职场打工人专属上班摸鱼划水微信小程序源码,没有后台 直接导入微信开发者工具即可运行,UI简约大气漂亮,只需登录微信公众...

    上班摸鱼软件

    在职场环境中,保持高效的工作状态至关重要,但有时候员工可能会寻找一些方法来放松自己,这就是“上班摸鱼”一词的由来。这个标题提到的“上班摸鱼软件”似乎是一种工具,旨在帮助用户在工作时间里进行短暂的休息...

    ft5406电容触摸IC数据手册

    FT5406是一款广泛应用在触摸屏设备中的电容式触摸集成电路,主要负责处理和解析来自触摸屏传感器的信号,从而实现对用户触摸操作的精确识别。本数据手册详细介绍了该芯片的功能特性、硬件设计、软件接口以及应用注意...

    摸鱼神器,单行小说阅读

    标题 "摸鱼神器,单行小说阅读" 暗示这是一款专为轻松阅读设计的软件或应用,可能特别适合工作或学习间隙快速阅读。它可能是以简洁、高效的单行显示方式来呈现小说内容,旨在让用户在短暂的休息时间里享受阅读的乐趣...

Global site tag (gtag.js) - Google Analytics