锁定老帖子 主题:再论要不要全程MockObject
该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2006-04-22
江南白衣 写道 robbin 写道 引用 现在多了一堆mockobject的接口和行为的二次定义
没有明白什么意思,写段代码解释一下? 原来函数的具体行为只有在真实代码里定义的。而现在需要在每次录制事件,也就是robbin讲的制定行为契约时,根据不同的上下文,重新定义一次。 所谓stub,就是相对减少上下文的影响,只在全局定义一次mock Object吧。 写个例子,我们分析分析? |
|
返回顶楼 | |
发表时间:2006-04-22
我先看看easy mock 2.1先:)
之前试的哦都是1.2 |
|
返回顶楼 | |
发表时间:2006-04-22
引用 原来函数的具体行为只有在真实代码里定义的。而现在需要在每次录制事件,也就是robbin讲的制定行为契约时,根据不同的上下文,每次重新定义一次。
所谓stub,就是相对减少上下文的影响,只在全局定义一次mock Object吧。 我写单元测试也好,写功能代码调用这个接口也好,是不必关心接口后面的具体行为的。因为接口契约已经明确指出了接口调用应该是什么行为。接口实现代码的具体行为必须符合接口契约,不管你这个接口实现代码是完整的实现的,还是mock出来的,都是符合接口契约的。如果你还关心接口后面的实现代码应该是什么行为,那么这个面向接口编程就彻底失败了,因为你拿到接口竟然无法预期具体的行为,那这个接口还有什么意义? 另外说到stub,我觉得编写的难度是超过mock的,特别是全局stub,更加耗费脑筋,而mock在一般情况下是省时省力的做法。不过对于调用特别频繁的通用接口或者类,我也会写stub,这个时候比用mock要节约很多代码。 |
|
返回顶楼 | |
发表时间: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 |
|
返回顶楼 | |
发表时间: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测试应该有其道理,但是对于应用类开发真的需要这样单元测试吗? |
|
返回顶楼 | |
发表时间:2006-04-23
更大的问题在重构时。
重构的定义:在不改变"软件之可察行为"前提下,调整其结构。 mock测试是如此的细致,甚至已经测试了”非可察行为“(我认为一个组件对其”助手类“进行了哪些调用、多少次调用完全是其家务事,不是可查行为); 那么重构势必导致原有单元测试失败,重构无法进行下去,因为你已经不能保证重构没有破坏原有功能。 |
|
返回顶楼 | |
发表时间: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了。 请继续举例... |
|
返回顶楼 | |
发表时间: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次或者干脆不执行这个操作了,这个测试就会失败。 |
|
返回顶楼 | |
发表时间: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下运行的学习一下。 |
|
返回顶楼 | |
发表时间:2006-04-23
总之不要全程使用mock测试
|
|
返回顶楼 | |