锁定老帖子 主题:什么是“测试驱动开发”
该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2006-04-24
gigix 写道 因为既有“惊讶”,也有“惊喜”。
八卦一下,惊喜在哪里? |
|
返回顶楼 | |
发表时间:2006-04-24
江南白衣 写道 gigix 写道 因为既有“惊讶”,也有“惊喜”。
八卦一下,惊喜在哪里? 惊喜有话说了亚…… |
|
返回顶楼 | |
发表时间:2006-04-24
robbin 写道 TDD是修改功能代码之前,先修改测试来表达你的实现方式,然后运行测试,fail,再修改功能代码,再运行测试,直到测试通过,则证明你的修改完成。
如果你的测试用例有问题,那怎么半?重构测试代码吗? gigix 写道 这里的测试是一个设计问题,而不是QA问题。
反应在哪呢? 先按照新的设计修改对应的单元测试,然后修改组件的实现使其通过所有(包括那个新的)测试, 这也是我的疑问。 |
|
返回顶楼 | |
发表时间:2006-04-24
这么说的话,TDD中测试至少有三层含义:
1. 测试是设计文档. 对应于功能实现 2. 测试是使用示例,对应于功能契约 3. 测试是安全网. 这个是测试的内在要求 应该说我们对2,3没什么不同意见. 但是,我一直觉得1和2在没有好的方法的情形下是有矛盾的. 白盒测试能规定出一定的设计要素,但是作为使用示例,显然是太罗嗦了.从某种意义上来说也是职责不清. 就拿前面的LoginController调用UserDAO.loadByName()方法这个例子,是否调用后者,从2的角度来看是透明的,而从1的角度来看必须把它固定下来. 这是一个矛盾.如果侧重于1,则对于那些相把测试用例当作方法的使用说明的人来言,这个就有点累了,首先看到很多mock的expect,然后replay,然后才是那个方法的使用主体,最后还有一堆verify和与结果有关和无关的assert. 而实际上关心的,只是使用部分,以及对于结果的assert.至于那个对象怎么初始化,怎么进行dependence injection的,他和那些依赖的对象之间怎么发生关系,那些依赖对象状态怎么变化,对于对象的使用者而言,是不关心的(而且也是不应当关心的). |
|
返回顶楼 | |
发表时间:2006-04-24
charon 写道 这么说的话,TDD中测试至少有三层含义:
1. 测试是设计文档. 对应于功能实现 2. 测试是使用示例,对应于功能契约 3. 测试是安全网. 这个是测试的内在要求 应该说我们对2,3没什么不同意见. 但是,我一直觉得1和2在没有好的方法的情形下是有矛盾的. 白盒测试能规定出一定的设计要素,但是作为使用示例,显然是太罗嗦了.从某种意义上来说也是职责不清. 就拿前面的LoginController调用UserDAO.loadByName()方法这个例子,是否调用后者,从2的角度来看是透明的,而从1的角度来看必须把它固定下来. 这是一个矛盾.如果侧重于1,则对于那些相把测试用例当作方法的使用说明的人来言,这个就有点累了,首先看到很多mock的expect,然后replay,然后才是那个方法的使用主体,最后还有一堆verify和与结果有关和无关的assert. 而实际上关心的,只是使用部分,以及对于结果的assert.至于那个对象怎么初始化,怎么进行dependence injection的,他和那些依赖的对象之间怎么发生关系,那些依赖对象状态怎么变化,对于对象的使用者而言,是不关心的(而且也是不应当关心的). 我对1也有疑问 ,对于编写测试代码的人,建立Mock对象,expect相应行为是设计的一个过程,测试编写完成之后也就意味者这个测试就是他脑子中的一份设计文档。 我不的不怀疑对于别的开发者来说,相对于这么一个“测试设计文档”--充斥着繁杂mock对象和expect行为的测试代码,是否更喜欢看简洁的源代码呢? 从这个角度来说,测试就是”设计文档“,是不是应该加一个定语: 仅仅编写测试代码的人的 |
|
返回顶楼 | |
发表时间:2006-04-24
charon 写道 这么说的话,TDD中测试至少有三层含义:
1. 测试是设计文档. 对应于功能实现 gigix说的是“TDD”才是设计,但是这个也许并不能等于TestCase就是设计文档了。从传统的意义上,一提到设计,人们想到的一般是架构、层次、耦合等等高层次的东西,但是单独的一个TestCase对于描述这些想法显然是力不从心的,它并不如白板上画出的UML更有效。 当然这没有诋毁TDD的意思,TDD强调的是两顶帽子小步前进,在不断小跑的过程中通过重复代码的消除,最终得到一个好的架构。本身这是一个很精妙的过程。但是对于TestCase本身来说,不管是描述这个过程还是描述过程带来的结果,自身的能力都是远远不够的,也许最终还是要靠程序员之间的交流和快速开发指南来说明这个东西。 |
|
返回顶楼 | |
发表时间:2006-04-24
firebody 写道 我不的不怀疑对于别的开发者来说,相对于这么一个“测试设计文档”--充斥着繁杂mock对象和expect行为的测试代码,是否更喜欢看简洁的源代码呢?
我也觉得,如果要我去看由mock Object, expect 和 assertEqual 组成的"文档",我还是回头看点别的舒服些。 这可能是职业素质问题,就像有些高手觉得函数式语言仿佛母语一般直白贴心,我就怎么都天书一样看不顺溜。 |
|
返回顶楼 | |
发表时间:2006-04-24
首先我自己也没有能够很好的进行TDD,目前还停留在边写功能代码,边写测试的阶段,不过在向TDD的方向努力。
上面大家讨论的问题,我想用一个简单的例子讨论一下: public class RenderPageInterceptorTest extends AbstractTestAction { protected ActionInvocation invocation; protected WikiService wikiService; protected RenderPageInterceptor interceptor; @Override protected void init() throws Exception { invocation = createMock(ActionInvocation.class); wikiService = createMock(WikiService.class); interceptor = new RenderPageInterceptor(); interceptor.setWikiService(wikiService); } public void testRenderPageAware() throws Exception { Page page = new Page(); page.setContent("hello world"); Template template = new Template(); template.setContent(" "); page.setTemplate(template); RenderPageAction action = new RenderPageAction(); action.setPage(page); expect(invocation.getAction()).andReturn(action); HttpServletRequest request = ServletActionContext.getRequest(); expect( wikiService.getPageWikiContent(action.getPage().getContent(), action.getPage().getTemplate() .getContent(), request)).andReturn("<html><body>hello world!</body></html>"); replay(invocation); replay(wikiService); action.execute(); interceptor.after(invocation, ActionSupport.SUCCESS); assertEquals("get wikicontent", "<html><body>hello world!</body></html>", action.getWikiContent()); verify(invocation); verify(wikiService); } } 这是对一个简单拦截器的测试。mock了ActionInvocation和WikiService,测试RenderPageInterceptor。 测试我现在会这样来写: 一、先写上replay和verify replay之前的代码是设计文档,而replay后面的是需求文档; 二、然后开始写 action.execute(); interceptor.after(invocation, ActionSupport.SUCCESS); assertEquals("get wikicontent", "<html><body>hello world!</body></html>", action.getWikiContent()); 这三行含义很明确,执行action,然后一个后拦截,最后断言action被拦截之后能否得到期望的wiki解析出来的html,这是需求文档部分。 三、接着写环境搭建部分代码 由于明确了执行流程,所以环境准备代码就清楚了,这包括init方法里面的 invocation = createMock(ActionInvocation.class); wikiService = createMock(WikiService.class); interceptor = new RenderPageInterceptor(); interceptor.setWikiService(wikiService); 这部分代码相当于替换spring容器和xwork容器的作用 四、然后就是测试数据填充部分 Page page = new Page(); page.setContent("hello world"); Template template = new Template(); template.setContent(" "); page.setTemplate(template); RenderPageAction action = new RenderPageAction(); action.setPage(page); 这部分代码等于模拟功能代码的外部数据交互过程 五、最后写mock的部分 expect(invocation.getAction()).andReturn(action); HttpServletRequest request = ServletActionContext.getRequest(); expect( wikiService.getPageWikiContent(action.getPage().getContent(), action.getPage().getTemplate() .getContent(), request)).andReturn("<html><body>hello world!</body></html>"); 第四部分和第五部分合起来,其实就是一个设计文档了。 我现在写测试基本上就是这种自下往上去写的过程,思路还是挺顺的。不过总体感觉写测试难度比功能代码要高很多。因为需要模拟spring容器和xwork容器,甚至servlet容器的操纵,要比写功能代码费很多心思了。当然我认为代价是值得的,就是我变更设计的时候,心里相当有信心。 |
|
返回顶楼 | |
发表时间:2006-04-24
robbin 写道 首先我自己也没有能够很好的进行TDD,目前还停留在边写功能代码,边写测试的阶段,不过在向TDD的方向努力
可以把那个Interceptor的代码放出来看嘛? 一个纯粹的感觉: 既然仅仅测试Interceptor那部分的逻辑,不如把它单独抽出来形成一个独立的模块,不依赖于ActionInvocation 也不依赖于xwork的拦截器,测试更加单纯一些。 不然你的代码还的需要mock action, action.execute() ...... |
|
返回顶楼 | |
发表时间:2006-04-24
public class RenderPageInterceptor extends AroundInterceptor { private WikiService wikiService; public void setWikiService(WikiService wikiService) { this.wikiService = wikiService; } @Override protected void after(ActionInvocation invocation, String result) throws Exception { Object action = invocation.getAction(); if (action instanceof RenderPageAware) { HttpServletRequest request = ServletActionContext.getRequest(); Page page = ((RenderPageAware) action).getPage(); String wikiContent = wikiService.getPageWikiContent(page.getContent(), page.getTemplate().getContent(), request); ((RenderPageAware) action).setWikiContent(wikiContent); } } @Override protected void before(ActionInvocation invocation) throws Exception { } } |
|
返回顶楼 | |