论坛首页 综合技术论坛

再论要不要全程MockObject

浏览 30447 次
该帖已经被评为精华帖
作者 正文
   发表时间:2006-04-23  
江南白衣 写道
其实说半天,还是没离开UnitTest的两个优点两个缺点
  优点:一是隔绝外部的干扰,外部模块的错误不会引起本模块的错误,能准确定位错误。二是stub的原理,大家可以分层分块开发。这两点都是封闭式UnitTest无可替代的优点。

  缺点一是代码量大而重复,Robbin 说了重复时用stub,但代码还是多阿。

  缺点二是无法保证所有录制的契约就是对象真实的行为,过了200个使用MockObject的UnitTest怎么都没有过了200个使用真实对象的Test安心阿。不知道Mock Object日后可不可以自动生成Unit Test来保证所声明过的契约的正确性。

  这两个缺点都是由两个优点所引出的,估计近期还不会改善。
 
  其实robbin也说了,Spring 有60%的mock object,那就是说Sping这种框架,很适于MockObject的都不是100%的使用率,那我们的BOSS, MIS型项目呢,又比如,开发顺序是先做Dao层,且数据清晰,那还有必要把全部dao mock掉,自己在ut里实现一个小数据库吗。
 
  又一个不恰当的比喻,类型检测那么重要的东西,大家看到Ruby这样的动态语言时,不是一个一个都见利忘义了吗。所以,我觉得测试,冒烟测试是最重要的,但是否与其他模块完全隔绝,就看实际需要与项目情形自己调配比例吧。
 
   第一要像Robbin一样,形成自己项目组的使用惯例,第二要赶紧把easyMock2.1 Retroweaver成能在JDK 1.4下运行的学习一下。

单元测试仅仅针对单个类的逻辑进行测试,模块间的交互逻辑和契约它不能很好的测试。集成测试需要同步的进行。但是,单元测试是最基本的测试。因为它能够排除90%的bug。
0 请登录后投票
   发表时间:2006-04-23  
江南白衣 写道
其实说半天,还是没离开UnitTest的两个优点两个缺点
  优点:一是隔绝外部的干扰,外部模块的错误不会引起本模块的错误,能准确定位错误。二是stub的原理,大家可以分层分块开发。这两点都是封闭式UnitTest无可替代的优点。

  缺点一是代码量大而重复,Robbin 说了重复时用stub,但代码还是多阿。

  缺点二是无法保证所有录制的契约就是对象真实的行为,过了200个使用MockObject的UnitTest怎么都没有过了200个使用真实对象的Test安心阿。不知道Mock Object日后可不可以自动生成Unit Test来保证所声明过的契约的正确性。

  这两个缺点都是由两个优点所引出的,估计近期还不会改善。
 
  其实robbin也说了,Spring 有60%的mock object,那就是说Sping这种框架,很适于MockObject的都不是100%的使用率,那我们的BOSS, MIS型项目呢,又比如,开发顺序是先做Dao层,且数据清晰,那还有必要把全部dao mock掉,自己在ut里实现一个小数据库吗。
 
  又一个不恰当的比喻,类型检测那么重要的东西,大家看到Ruby这样的动态语言时,不是一个一个都见利忘义了吗。所以,我觉得测试,冒烟测试是最重要的,但是否与其他模块完全隔绝,就看实际需要与项目情形自己调配比例吧。
 
   第一要像Robbin一样,形成自己项目组的使用惯例,第二要赶紧把easyMock2.1 Retroweaver成能在JDK 1.4下运行的学习一下。


建议你试试agitar公司的agitator,获得第15届Jolt winner of Automated Test Tools奖项,一定程度上解决了你描述的一部分问题,据我所知IBM,HP内部有些开发组在使用之,不过其license相当贵。
0 请登录后投票
   发表时间:2006-04-23  
有一点是肯定的: 不要全程使用Mock对象!

在我的应用项目中,我是这样选择测试方案的:

项目使用的框架是Hibernate/Spring/Webwork,分层结构是:Web Action/Service/DAO/DomainObject (经常会合并Service和DAO,不过为了讨论的完备性,这里假设DAO和Service分开)。

首先DomainObject自身需要测试的逻辑很少,不需要提了。

然后DAO接口的测试,这里就存在不同的选择:

1) 启动spring容器,初始化sessionFactory,去访问一个真正的数据库进行测试;
2) 启动spring容器,初始化sessionFactory,但是不访问数据库,使用dbunit;
3) 根本不启动spring容器,mock sessionFactory和session;

上面daquan198163提供了一个很好的mock sessionFactory和session的例子,而flyingbug提出框架测试和应用测试是不同的。具体到现在这个例子来说,我曾经思考过DAO接口测试的选择方案。上面两位说的都没错,针对这个DAO接口测试的场景来说框架测试和应用测试是不同的。

大家可以看到spring对Hibernate/JDBC访问API的测试完全使用easymock,而不访问真实的数据库,也不使用dbunit。而如果我们的应用项目,我想大家没有人会选择mock sessionFactory,而肯定会选择访问数据库或者dbunit。

根本的原因在于:spring测试的目标是API的执行逻辑是否正确,而我们应用测试的目标是我的查询语句是否写的正确

举个最简单例子来说,spring的HibernateTemplate测试HibernateTemplateTests的testFind
	public void testFind(); throws HibernateException {
		MockControl queryControl = MockControl.createControl(Query.class);;
		Query query = (Query); queryControl.getMock();;

		List list = new ArrayList();;
		sf.openSession();;
		sfControl.setReturnValue(session, 1);;
		session.getSessionFactory();;
		sessionControl.setReturnValue(sf, 1);;
		session.createQuery("some query string");;
		sessionControl.setReturnValue(query, 1);;
		query.list();;
		queryControl.setReturnValue(list, 1);;
		session.flush();;
		sessionControl.setVoidCallable(1);;
		session.close();;
		sessionControl.setReturnValue(null, 1);;
		sfControl.replay();;
		sessionControl.replay();;
		queryControl.replay();;

		HibernateTemplate ht = new HibernateTemplate(sf);;
		List result = ht.find("some query string");;
		assertTrue("Correct list", result == list);;
		queryControl.verify();;
	}

这里查询语句甚至使用"some query string",因为spring的测试根本就不关注查询正确与否,它要测试的目标是Template.find方法对Hibernate的Query.list()的封装代码是否是正确的,至于query.list的运行逻辑,他完全不关心,完全不管他访问数据库,还是访问XML。

所以由于测试目标是不同的,spring在这里使用easymock是非常正确的选择,当然spring也可以初始化spring容器访问真实的数据库,但是这就变成了完全没有必要的overhead了。

反过来看我们应用项目的测试,假设我的userDao封装了HibernateTemplate,提供了一个findUsers() 方法,按照某种条件查询用户,那么我们的UserDaoTest的测试目标并不是HibernateTemplate对Hibernate的Query封装逻辑是否正确,也不是我们的userDao对HibernateTemplate的封装逻辑是否正确,而是userDao能否按照期望发送正确的SQL语句,能否按照期望查询出来正确的结果,因此如果我们再选择对sessionFactory和session进行mock,那根本就是错误的测试,而只有访问真实的数据库,才能得到最符合运行环境的测试结果,所以我们最好的方案就是启动spring容器,访问真实的数据库去做测试,而不要用动态mock

所以,spring框架的测试和我们应用的测试关注的测试目标根本就是不同的,当然需要选择不同的测试策略。但是我要说明的是,spring的test里面对sessionFactory和session进行mock是非常正确的选择,如果引入对数据库的访问,这反而是非常不必要的overhead。

接着,我们来看Serivce层。Service层关注的是业务逻辑是否正确,完全不关注数据库访问逻辑,因此我们需要对Service依赖的DAO接口使用mock(最好是动态mock),在这里,如果我们去启动spring容器,访问真实的数据库,获得真正的DAOImpl,也是不必要的overhead,道理完全等同于spring对HibernateTemplate的测试。

最后是Web Action的测试,同理,我们使用动态mock去mock Action依赖的Service接口。但是这里有点特殊的是Servlet API。他是非常通用而且调用频繁的API,而且每次都调用那几个特定的method,如果每次都动态mock,成本显然太高了,这里就不如专门写一个stub类了。

综上所述,我的应用项目测试策略就是这样的,不同的测试目标决定了你选择什么样的测试方案,而easymock的使用使我可以轻松的分离Service对DAO的依赖,轻松的分离Action对Service的依赖。
0 请登录后投票
   发表时间:2006-04-23  
引用
spring测试的目标是API的执行逻辑是否正确,而我们应用测试的目标是我的查询语句是否写的正确

引用
spring框架的测试和我们应用的测试关注的测试目标根本就是不同的,当然需要选择不同的测试策略。

引用
如果每次都动态mock,成本显然太高了,这里就不如专门写一个stub类了。


如果你一开始的观点就是这样,我就真不知道到底我们为什么要讨论那么久了


大周末的讨论这个真挺累人的,我闪了
0 请登录后投票
   发表时间:2006-04-24  
flyingbug 写道

如果你一开始的观点就是这样,我就真不知道到底我们为什么要讨论那么久了
大周末的讨论这个真挺累人的,我闪了


这种讨论挺好的呀,你敢说自己没从中得到提高吗?
0 请登录后投票
   发表时间:2006-06-04  
easyMock极为不可读,我觉得jmock才是mock的最佳选项

Mock people = mock(People.class);;
people.expect(once(););.method(“eat”);.will(throwException(enoughFoodException););;


通常我会把括号显示成比较浅的颜色
所以以上的录制code就成了
Mock people = mock People.class ;
people.expect once   .method “eat” .will throwException enoughFoodException ;

通常能读应为的人都回很容易理解上面的这段code再做什么



江南白衣 写道
MockObject的好处:
第一、隔绝其他模块出错引起本模块的测试错误。
第二、隔绝其他模块的开发状态,只要定义了接口,不用管隔壁那条友开发完成没有,debug干净没有。
第三、一些速度较慢的操作,可以用mockObject代替,快速返回。

   但MockObject也有不爽的地方,所以,我的观点仍然是,团队应该以自己的开发顺序,人员与模块划分,决定某个TestCase是否使用MockObject。其实和Every Class should be Interface Base一样,大牛们推荐的是最好的东西,但我们仍然要以Pragmatic的批判精神,按实际情况选用。

   MockObject不爽的地方:
  第一是事件录制的代码量相当大,而且干扰TestCase的可读性。比如函数返回一个Book,我就要手工设置这个Book所有测试相关的属性,如果返回的是Book List,那就更加.....如果分支很多,你还要不断重复录制....比如它如果有嵌套的函数调用,还要一个个录下去....看到眼花时,还很干扰你的测试校验代码。

    第二是代码重复的bad smell,比如ApplicationContext.xml定义好了的对象管理关系,Test里又手工设置了一次。
   更重要是本来bookManager的sell方法,无论是接口和行为定义都只有一个地方,引入mockObject后,每一个录制mockObject的地方,就多了一个对该函数接口和行为的定义,代码修改和重构时很麻烦(注意,这和unit test对重构的支持是两个不同的概念)

   因此,如果强制全程推行的话,代码量那是相当壮观,lighter,faster java的口号也白喊了,什么lighweight框架省下的代码还不够ut里填的。 1行代码,10行测试对于国内大部分资源少,时间紧的团队来说还是太奢侈了。

  而且,ut很重要,但ut不能代替集成测试,所以最终还是要有no mock object的集成测试test case
0 请登录后投票
   发表时间:2006-06-04  
因为需要才mock,不是因为mock所以需要,
是不是全程又有什么关系
0 请登录后投票
   发表时间:2006-10-24  
越来越觉得TDD不错,不过不知道大家对极为简单的方法,如只是一个代理性质的方法写单元测试吗?
0 请登录后投票
   发表时间:2006-11-16  
很好,我也认为跟策略有关,我对于DAO的测试基本上采用集成测试,即直接访问数据库,但当那部分有逻辑时,我还会加上plain的TestCase,如当我需要构建一个SQL,而这个SQL随着传入的参数不同而变化时,我就会加上Plain TestCase。

此外,我还有个问题:
就是如果采用内部类怎么测?这种情况很常见,比如Callback一般就是这种,像用于HibernateTemplate的HibernateCallback。我怎么mock doInHibernate方法的Session?
0 请登录后投票
   发表时间:2006-11-17  
有两个感觉在我看来是很相似的,都是烦恼:
1、不用Mock框架,自己手写Mock或者Stub的时候,在一堆单元测试中实现好多接口,但是都是假的。要在接口中加一个方法,改点东西的时候,累死……
2、使用Mock,大量使用基于交互的测试方式。改一点代码逻辑,对同一个接口的真实实现和Mock实现要修改十遍以上,累死……
在我们现在的项目中,分出Local和Dev两种环境。对于Dev环境我们写Smoke测试来访问真实数据库,无论是JDBC直接访问还是Hibernate访问。对于Local环境,我们对于JDBC的访问,采用Stub的DAO方式。对于Hibernate则采用HSQL数据库,往里插测试数据。然后把Selenium测试跑在Local环境上测除了数据访问之外的逻辑。在Smoke测试和Selenium测试之外,剩下的就是中间的单元测试了,它们不管数据访问,不管UI,只管业务逻辑。
0 请登录后投票
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics