锁定老帖子 主题:什么是“测试驱动开发”
该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2006-04-24
robbin 写道 2、replay之前的测试代码显然是设计文档,,如果你关心功能代码的实现细节,就可以看这一部分。 这种说法是不是有些牵强。我认为这里的代码也就能起到比java-doc更高档一点的功能,但是要说是设计文档,好像还差好多。 对于需要写sql的Dao层的测试更是如此,在这里单纯的mock意义不大,我更关心sql是否能正确执行的问题。可是面对只有init sql的代码,我能看出dao层的任何设计吗?分层清晰的话我知道也许能到业务层碰碰运气看能否找到dao层的模拟代码,不过这个精力花的就太不值得了。 |
|
返回顶楼 | |
发表时间:2006-04-24
yuxie 写道 robbin 写道 2、replay之前的测试代码显然是设计文档,,如果你关心功能代码的实现细节,就可以看这一部分。 这种说法是不是有些牵强。我认为这里的代码也就能起到比java-doc更高档一点的功能,但是要说是设计文档,好像还差好多。 对于需要写sql的Dao层的测试更是如此,在这里单纯的mock意义不大,我更关心sql是否能正确执行的问题。可是面对只有init sql的代码,我能看出dao层的任何设计吗?分层清晰的话我知道也许能到业务层碰碰运气看能否找到dao层的模拟代码,不过这个精力花的就太不值得了。 这个是Web Action的测试代码,不是你想要看的DAO的测试代码。你要看的是这个帖子: http://forum.iteye.com/viewtopic.php?t=20035 |
|
返回顶楼 | |
发表时间:2006-04-24
测试驱动开发在推进模块化开发,解耦各个模块建的依赖确实是一个推进的作用,
然而,从目标来看,它是以模块开发,解除耦合,易于测试为目标 。 这样的构思虽然是在测试代码里得到体现,但是所有的构思仍然是脑子的构思行为。 如果脑子里面的构思紧紧围绕“模块开发,解除耦合,易于测试“”这个设计宗旨的话,那么又何必要强调谁先谁后呢? 当然,谁都不保证脑子是否足够清醒,但是如果有重构的保证,我觉得测试先行 就 失去其最大价值了。 举这么一个通用一点的例子: 需要构思一个逻辑,这个逻辑初步设计用一个类来封装。 OK,测试先行: Comp comp = new Comp();; comp.doLogic(.....);; assertEquals(...,comp.getResult(.); );; 测试失败,编译不通过。 ok,创建component类,增加doLogic方法。 编译通过,测试失败。 接着构思一下设计,这个组件需要依赖comp1,comp2。 逻辑里面需要调用comp1.logic1(),comp2.logic2()方法来完成特定的逻辑,然后作本身的业务逻辑 . ok,脑子里的基本设计好了,又开始完善设计。 Comp1 mockComp1 = createMock(Comp1);; Comp2 mockComp2 = createMock(Comp2);; //反映依赖 Comp comp = new Comp(mockComp1,mockComp2);; //反映设计的逻辑 expect(mockComp1.logic(););.andReturn(..);; expect(mockComp2.logic2(););.andReturn();.times(1);; comp.doLogic();; assertEquals(...,comp.getResult(.); );; //验证测试 relpaly(..); verify(); 运行测试失败 按照构思,写全COmponent的logic的方法代码。 测试通过。 这是一个我所理解的基本完整的TDD的开发例子。 然而,这里有一个疑惑,虽然TDD的结果是一个很好的设计实现,但是这个设计实现仍然可以在我的脑子里面基于“模块解耦,易测试“的宗旨 得出来。然后,这个构思通过直接编写Component的实现之后,再补全这个测试扬例 ,虽然这里有可能会导致偷懒不写测试的问题,嘿嘿.......... 好的,现在开始提出我的疑惑: 假设要构建的Component.doLogic的方法逻辑足够复杂,给出一个模拟的伪代码: COmponent: private void doSomeInternalLogic1(..);{.....} private void doSomeInternalLogic2(..);{ .... } public void doLogic();{ Object result = comp1.logic1(); if(result....);{ Object result2 = comp2.logic2();; doSomeInternalLogic1(result,result2);; } else{ Object result2 = comp2.logic3();; doSomeInternalLogic2(result,result3);; } } } 反映编写者设计意图的测试如下: Comp1 mockComp1 = createMock(Comp1);; Comp2 mockComp2 = createMock(Comp2);; //反映依赖 Comp comp = new Comp(mockComp1,mockComp2);; //反映设计的逻辑 //条件分支1 Object result = ...//模拟符合条件分支跳转的数据 expect(mockComp1.logic1(););.andReturn(result);.times(1);; //条件分支1将执行特定的逻辑logic2(); expect(mockComp1.logic2(););.andReturn(result);.tim2s(1);; Ok,上面的代码反应了测试的一个意图,但是并没有反应用户全部的设计意图,因为那个doSomeInternalLogic1方法的逻辑在测试代码里面是得不到反映的。 为什么得不到反映呢? 因为他没有涉及外部组件的交互,仅仅是内部状态的逻辑处理。我这里为了突出这点,给了他一个internal的名称。 假设这个doSomeInternalLogic1方法的代码有几十行的话,那么他逻辑的分量比重就很大了,从这点来说,测试代码没有完全反映组件的设计意图,仅仅反映了组件跟外部组件的交互意图。 |
|
返回顶楼 | |
发表时间:2006-04-25
partech 写道 赫赫,终于有人跳出来,讨论“测试驱动开发”了。
俺先提一个想了很久的问题: TDD要求先写出测试,然后让它通过。我的疑问是:“对于要开发的东西,除非有了充分的认识,否则,首先写测试,是很困难(如果你说不困难,那就是俺笨),该咋办?” 每一条测试,实质上是一条需求。如果说对要开发东西的需求都不明确,无论用什么方式开发都很困难。 |
|
返回顶楼 | |
发表时间:2006-04-25
doSomeInternalLogic1似乎应该抽象出来单独测试吧?
|
|
返回顶楼 | |
发表时间:2006-04-25
robbin 写道 doSomeInternalLogic1似乎应该抽象出来单独测试吧?
虽然在我写代码的时候,心里想过没有必要,但这确实很有意思的一个提议。 我认为它是好提议在于几个方面: 1)至少从概念上来说符合了模块分离的概念 2)能够从测试代码上反映这个设计 ok,我立即动手: interface ComponentInternalLogic{ void doSomeInternalLogic1(Object result,Object ....);; void doSomeInternalLogic2(Object result,Object ....);; } 原来的测试代码改成如下: Comp1 mockComp1 = createMock(Comp1);; Comp2 mockComp2 = createMock(Comp2);; ComponentInternalLogic mockInternal = createMock(ComponentInternalLogic.class);; //增加被分离的InternalService模块,为了使得它能够在测试中反映这个逻辑,//以及考虑模块分离(是否有点过渡设计的味道呢? 因为现在还不足以抽出来!); Inter //反映依赖 Comp comp = new Comp(mockComp1,mockComp2,mockInternal );; //反映设计的逻辑 //条件分支1 Object result = ...//模拟符合条件分支跳转的数据 expect(mockComp1.logic1(););.andReturn(result);.times(1);; //条件分支1将执行特定的逻辑logic2(); Object result2 - .. // expect(mockComp1.logic2(););.andReturn(result2 );.tim2s(1);; //很惊奇的分离了本来是内部的一个方法,以反映设计的模块化 mockInternal.doSomeInternalLogic1(result,result2);; ok,这个设计到此已经促成了ComponentInternalLogic 的分离。 现在光是看测试代码确实能够反映Component内部的所有的设计逻辑了。 但是,这里有一个疑惑,是否使得过渡设计的味道出来了呢? 因为单纯成代码来看,没有必要抽取ComponentInternalLogic 出来的。 |
|
返回顶楼 | |
发表时间:2006-04-25
奇怪了,讨论这么久了怎么不见贴主:gigix 出来帮我们解答疑惑呢?
呵呵。 |
|
返回顶楼 | |
发表时间:2006-04-25
我记得以前某人说过,TDD驱动开发出来的功能代码,每个method平均3行。
这就说明TDD驱动设计和开发最终会导致你的功能代码每个method的职责非常单一,凡是一个method承担了超过一个职责,那么就必须把其他职责分离出来。 如果每个method只有一个单一职责的话,测试代码就确实能够准确的反映功能逻辑,不会出现功能代码的内部无法测试的流程。这也反过来说,如果你的测试无法反映你的功能代码逻辑,就说明你的功能代码偶合性还是过高。 |
|
返回顶楼 | |
发表时间:2006-04-25
robbin 写道 我记得以前某人说过,TDD驱动开发出来的功能代码,每个method平均3行。
这就说明TDD驱动设计和开发最终会导致你的功能代码每个method的职责非常单一,凡是一个method承担了超过一个职责,那么就必须把其他职责分离出来。 如果每个method只有一个单一职责的话,测试代码就确实能够准确的反映功能逻辑,不会出现功能代码的内部无法测试的流程。这也反过来说,如果你的测试无法反映你的功能代码逻辑,就说明你的功能代码偶合性还是过高。 虽然是有理,但是单纯从上面的代码来看,我有种过渡设计/分离的味道,因为从代码的简洁程度来说,把一个只有十多行代码的method抽出到一个专门的接口,然后再用一个实现类来作,最好才能体现TDD就是设计的好处,我对此持有疑惑。 |
|
返回顶楼 | |
发表时间:2006-04-25
象firebody这样的话 会不会类的个数膨胀了不少 !
本来是某个类的私有方法 但为了更好的测试 只能添加到助手类中 这样view code的时候 不是更不方便了 类也多了2个(一个接口 一个实现类)? |
|
返回顶楼 | |