论坛首页 综合技术论坛

什么是“测试驱动开发”

浏览 71461 次
该帖已经被评为精华帖
作者 正文
   发表时间:2006-04-24  
robbin 写道

2、replay之前的测试代码显然是设计文档,,如果你关心功能代码的实现细节,就可以看这一部分。


这种说法是不是有些牵强。我认为这里的代码也就能起到比java-doc更高档一点的功能,但是要说是设计文档,好像还差好多。
对于需要写sql的Dao层的测试更是如此,在这里单纯的mock意义不大,我更关心sql是否能正确执行的问题。可是面对只有init sql的代码,我能看出dao层的任何设计吗?分层清晰的话我知道也许能到业务层碰碰运气看能否找到dao层的模拟代码,不过这个精力花的就太不值得了。
0 请登录后投票
   发表时间: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
0 请登录后投票
   发表时间: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方法的代码有几十行的话,那么他逻辑的分量比重就很大了,从这点来说,测试代码没有完全反映组件的设计意图,仅仅反映了组件跟外部组件的交互意图。
0 请登录后投票
   发表时间:2006-04-25  
partech 写道
赫赫,终于有人跳出来,讨论“测试驱动开发”了。

俺先提一个想了很久的问题:
TDD要求先写出测试,然后让它通过。我的疑问是:“对于要开发的东西,除非有了充分的认识,否则,首先写测试,是很困难(如果你说不困难,那就是俺笨),该咋办?”


每一条测试,实质上是一条需求。如果说对要开发东西的需求都不明确,无论用什么方式开发都很困难。
0 请登录后投票
   发表时间:2006-04-25  
doSomeInternalLogic1似乎应该抽象出来单独测试吧?
0 请登录后投票
   发表时间: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  出来的。
0 请登录后投票
   发表时间:2006-04-25  
奇怪了,讨论这么久了怎么不见贴主:gigix 出来帮我们解答疑惑呢?

呵呵。
0 请登录后投票
   发表时间:2006-04-25  
我记得以前某人说过,TDD驱动开发出来的功能代码,每个method平均3行。

这就说明TDD驱动设计和开发最终会导致你的功能代码每个method的职责非常单一,凡是一个method承担了超过一个职责,那么就必须把其他职责分离出来。

如果每个method只有一个单一职责的话,测试代码就确实能够准确的反映功能逻辑,不会出现功能代码的内部无法测试的流程。这也反过来说,如果你的测试无法反映你的功能代码逻辑,就说明你的功能代码偶合性还是过高。
0 请登录后投票
   发表时间:2006-04-25  
robbin 写道
我记得以前某人说过,TDD驱动开发出来的功能代码,每个method平均3行。

这就说明TDD驱动设计和开发最终会导致你的功能代码每个method的职责非常单一,凡是一个method承担了超过一个职责,那么就必须把其他职责分离出来。

如果每个method只有一个单一职责的话,测试代码就确实能够准确的反映功能逻辑,不会出现功能代码的内部无法测试的流程。这也反过来说,如果你的测试无法反映你的功能代码逻辑,就说明你的功能代码偶合性还是过高。

虽然是有理,但是单纯从上面的代码来看,我有种过渡设计/分离的味道,因为从代码的简洁程度来说,把一个只有十多行代码的method抽出到一个专门的接口,然后再用一个实现类来作,最好才能体现TDD就是设计的好处,我对此持有疑惑。
0 请登录后投票
   发表时间:2006-04-25  
象firebody这样的话 会不会类的个数膨胀了不少 !
本来是某个类的私有方法 但为了更好的测试 只能添加到助手类中 这样view code的时候 不是更不方便了 类也多了2个(一个接口 一个实现类)?
0 请登录后投票
论坛首页 综合技术版

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