论坛首页 综合技术论坛

再论要不要全程MockObject

浏览 30446 次
该帖已经被评为精华帖
作者 正文
   发表时间:2006-04-22  
江南白衣 写道
robbin 写道
引用
现在多了一堆mockobject的接口和行为的二次定义


没有明白什么意思,写段代码解释一下?



   原来函数的具体行为只有在真实代码里定义的。而现在需要在每次录制事件,也就是robbin讲的制定行为契约时,根据不同的上下文,重新定义一次。

  所谓stub,就是相对减少上下文的影响,只在全局定义一次mock Object吧。


写个例子,我们分析分析?
0 请登录后投票
   发表时间:2006-04-22  
我先看看easy mock 2.1先:)
之前试的哦都是1.2
0 请登录后投票
   发表时间:2006-04-22  
引用
原来函数的具体行为只有在真实代码里定义的。而现在需要在每次录制事件,也就是robbin讲的制定行为契约时,根据不同的上下文,每次重新定义一次。

所谓stub,就是相对减少上下文的影响,只在全局定义一次mock Object吧。


我写单元测试也好,写功能代码调用这个接口也好,是不必关心接口后面的具体行为的。因为接口契约已经明确指出了接口调用应该是什么行为。接口实现代码的具体行为必须符合接口契约,不管你这个接口实现代码是完整的实现的,还是mock出来的,都是符合接口契约的。如果你还关心接口后面的实现代码应该是什么行为,那么这个面向接口编程就彻底失败了,因为你拿到接口竟然无法预期具体的行为,那这个接口还有什么意义?

另外说到stub,我觉得编写的难度是超过mock的,特别是全局stub,更加耗费脑筋,而mock在一般情况下是省时省力的做法。不过对于调用特别频繁的通用接口或者类,我也会写stub,这个时候比用mock要节约很多代码。
0 请登录后投票
   发表时间:2006-04-22  
这是一个待测类
public class LoginServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response); throws ServletException, IOException {
        String username = request.getParameter("username");;
        String password = request.getParameter("password");;
        // check username & password:
        if("admin".equals(username); && "123456".equals(password);); {
            ServletContext context = getServletContext();;
            RequestDispatcher dispatcher = context.getNamedDispatcher("dispatcher");;
            dispatcher.forward(request, response);;
        }
        else {
            throw new RuntimeException("Login failed.");;
        }
    }
}

测试
MockControl requestCtrl = MockControl.createControl(HttpServletRequest.class);;
    HttpServletRequest requestObj = (HttpServletRequest);requestCtrl.getMock();;
    MockControl contextCtrl = MockControl.createControl(ServletContext.class);;
    final ServletContext contextObj = (ServletContext);contextCtrl.getMock();;
    MockControl dispatcherCtrl = MockControl.createControl(RequestDispatcher.class);;
    RequestDispatcher dispatcherObj = (RequestDispatcher);dispatcherCtrl.getMock();;

按照doPost();的语句顺序,我们设定Mock对象指定的行为:

    requestObj.getParameter("username");;
    requestCtrl.setReturnValue("admin", 1);;
    requestObj.getParameter("password");;
    requestCtrl.setReturnValue("123456", 1);;
    contextObj.getNamedDispatcher("dispatcher");;
    contextCtrl.setReturnValue(dispatcherObj, 1);;
    dispatcherObj.forward(requestObj, null);;
    dispatcherCtrl.setVoidCallable(1);;
    requestCtrl.replay();;
    contextCtrl.replay();;
    dispatcherCtrl.replay();;

然后,测试doPost();方法,这里,为了让getServletContext();方法返回我们创建的ServletContext Mock对象,我们定义一个匿名类并覆写getServletContext();方法:

    LoginServlet servlet = new LoginServlet(); {
        public ServletContext getServletContext(); {
            return contextObj;
        }
    };
    servlet.doPost(requestObj, null);;

最后,检查所有Mock对象的状态:

    requestCtrl.verify();;
    contextCtrl.verify();;
    dispatcherCtrl.verify();;

例子转自http://www.crackj2ee.com/Article/ShowArticle.asp?ArticleID=385
0 请登录后投票
   发表时间:2006-04-23  
这个例子不一定恰当,我想说的是mock测试与实现类存在重复,也就是白衣提到的“mockobject的接口和行为的二次定义”。比如:
requestObj.getParameter("username");;
    requestCtrl.setReturnValue("admin", 1);;
    requestObj.getParameter("password");;
    requestCtrl.setReturnValue("123456", 1);;
    contextObj.getNamedDispatcher("dispatcher");;
    contextCtrl.setReturnValue(dispatcherObj, 1);;
    dispatcherObj.forward(requestObj, null);;
    dispatcherCtrl.setVoidCallable(1);;

这段测试代码是要与实现代码保持对应的,测试类需要知道实现类调用了其他组件的哪些方法甚至调用的次数,Spring大量采用mock测试应该有其道理,但是对于应用类开发真的需要这样单元测试吗?
0 请登录后投票
   发表时间:2006-04-23  
更大的问题在重构时。
重构的定义:在不改变"软件之可察行为"前提下,调整其结构。

mock测试是如此的细致,甚至已经测试了”非可察行为“(我认为一个组件对其”助手类“进行了哪些调用、多少次调用完全是其家务事,不是可查行为);
那么重构势必导致原有单元测试失败,重构无法进行下去,因为你已经不能保证重构没有破坏原有功能。
0 请登录后投票
   发表时间:2006-04-23  
daquan198163 写道
更大的问题在重构时。
重构的定义:在不改变"软件之可察行为"前提下,调整其结构。

mock测试是如此的细致,甚至已经测试了”非可察行为“(我认为一个组件对其”助手类“进行了哪些调用、多少次调用完全是其家务事,不是可查行为);
那么重构势必导致原有单元测试失败,重构无法进行下去,因为你已经不能保证重构没有破坏原有功能。


你上面举的那个例子相当的糟糕,因为在这个例子中的request对象恰恰是应该使用stub,而不是使用动态mock的场合。我上面说到:
引用
不过对于调用特别频繁的通用接口或者类,我也会写stub,这个时候比用mock要节约很多代码。


像这种通用接口类被调用的很频繁,写stub是值得的。在我开发的项目中,特别是Web层,也会大量调用request,response这种东西,我通常都会写这两个静态stub对象(不太喜欢用spring提供的这两个Mock对象),而不会对他们使用easymock。这种情况确实不适合使用easymock,会导致测试代码可读性和可维护性很差。

总得来说这也算一种权衡吧,例如我写InterceptorTest,就会使用easymock去mock ActionInvocation,如果你每个InterceptorTest都去stub ActionInvocation,,代码就膨胀的太厉害了。目前我主要使用easymock去mock Service接口和DAO接口,以及其他少量使用但是不太容易stub出来的接口。对于那些非常通用的接口,当然要stub了。


请继续举例...
0 请登录后投票
   发表时间:2006-04-23  
那个例子没什么问题吧,你就假设它不是通用接口不就行了。
Spring的org.springframework.orm.hibernate3.support.OpenSessionInViewTests
MockControl sfControl = MockControl.createControl(SessionFactory.class);;
		final SessionFactory sf = (SessionFactory); sfControl.getMock();;
		MockControl sessionControl = MockControl.createControl(Session.class);;
		Session session = (Session); sessionControl.getMock();;

		OpenSessionInViewInterceptor interceptor = new OpenSessionInViewInterceptor();;
		interceptor.setSessionFactory(sf);;
		MockServletContext sc = new MockServletContext();;
		MockHttpServletRequest request = new MockHttpServletRequest(sc);;
		MockHttpServletResponse response = new MockHttpServletResponse();;

		sf.openSession();;
		sfControl.setReturnValue(session, 1);;
		session.getSessionFactory();;
		sessionControl.setReturnValue(sf, 2);;
		session.isOpen();;
		sessionControl.setReturnValue(true, 1);;
		session.setFlushMode(FlushMode.NEVER);;
		sessionControl.setVoidCallable(1);;
		sfControl.replay();;
		sessionControl.replay();;
		interceptor.preHandle(request, response, "handler");;
		assertTrue(TransactionSynchronizationManager.hasResource(sf););;

		// check that further invocations simply participate
		interceptor.preHandle(request, response, "handler");;

		assertEquals(session, SessionFactoryUtils.getSession(sf, false););;

		interceptor.preHandle(request, response, "handler");;
		interceptor.postHandle(request, response, "handler", null);;
		interceptor.afterCompletion(request, response, "handler", null);;

		interceptor.postHandle(request, response, "handler", null);;
		interceptor.afterCompletion(request, response, "handler", null);;

		interceptor.preHandle(request, response, "handler");;
		interceptor.postHandle(request, response, "handler", null);;
		interceptor.afterCompletion(request, response, "handler", null);;

		sfControl.verify();;
		sessionControl.verify();;

其中sessionControl.setReturnValue(sf, 2);断言session.getSessionFactory();会执行2次,如果某一天我重构OpenSessionInViewInterceptor内部实现,执行session.getSessionFactory()3次或者干脆不执行这个操作了,这个测试就会失败。
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下运行的学习一下。
0 请登录后投票
   发表时间:2006-04-23  
总之不要全程使用mock测试
0 请登录后投票
论坛首页 综合技术版

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