Hello World
Spiga

我的TDD实践:可测试性驱动开发(上)

2009-10-15 13:34 by 老赵, 22003 visits

TDD(测试驱动开发,Test Driven Development)是重要的敏捷实践之一,它的基本原理是用测试来带动开发,先写测试代码,再写开发代码,最后重构。许多TDD推广和实践者认为,这种方式易于带来高质量的代码。而如今,TDD也慢慢有了Test Driven Design,也就是测试驱动设计的意味。也就是说,它更像是一种设计方式了。这些理论我很愿意相信,也很支持,但是从实际角度来说,我还是较难接受正统的TDD行为。不过,我也在实际开发过程中总结出……怎么说呢,应该说是更适合我自己的实践方式,在此希望能和大家交流一下。

我难以接受正统TDD方式的原因,在于我总是过于习惯在拿到一个需求的时候,在脑海里率先出现设计。而正统TDD的要求应该是先从测试代码开始,但是我脑海中已经出现了设计“草图”之后,写出来的测试也已经有相当明确的“导向性”了——那么,即使我先写测试,又有什么意义呢?而且,我在写测试的时候,总是在想“哎,这个测试真多余,反正最终代码不会仅仅是这样的”。对于我来说,我只能采取正统TDD方式的“形”,而实在接受不了它的“神”。

至今我还在疑惑,因为我觉得普通开发人员像我这样情况其实应该也有不少,那么对于像我这样的人,又该如何采用TDD的方式来开发项目呢?最终我放弃了使用TDD,不过单元测试是一定保留了下来的。

于是,我还是先写代码,再写测试,用测试来检查代码的实现和“期望”是否相符。接着,为了提高项目测试的可测试性,我会不断重构代码,分离职责,构造一些功能明确的辅助类等等。慢慢的慢慢的,似乎我觉得最后得到的成果还是相当有模有样的。忽然有一天,我觉得自己的做法也已经形成了一些“套路”,我一时兴起在推特上“宣称”我在使用一种叫做“测试导向开发”的方式,因为我时刻考虑代码该如何测试,为此而不断改变我的设计。

测试导向开发,即Test Targeting Development或TTD。当然最后一个D改为Design似乎也没有什么问题。

与传统TDD的开发方式不同,我的TTD方式还是先写代码,后写测试。只不过,我会时刻关注自己的代码是否容易测试,并不断重构产品代码和测试代码。基本上它的步骤是:

  1. 写产品代码
  2. 为产品代码写测试
  3. 发现测试不容易写,于是重构产品代码
  4. 重构测试
  5. ……

一般来说,这几个步骤的执行顺序都比较随意,唯一的目的便是在产品开发过程中,让产品代码得到更多的测试覆盖率。这会迫使我们编写更加容易测试的代码,而我慢慢发现这个要求很接近于著名的SOLID原则

  • 单一职责原则(Single Resposibility Principle):如果一个类的职责不单一,我写单元测试的时候就要准备一个复杂的初始数据,然后劳心劳力地推测出它的输出是什么。此时,我会把一部分职责抽象成外部类,然后再某种方式交由原来的类使用。在单元测试的时候,我可以为新生成的外部类构造Stub,也可以为这个外部类做额外的单元测试。
  • 开/闭原则(Open/Close Principle):这个似乎和单元测试的关系不大,符合这个原则更多是为了更好的产品设计。当然,单元测试本身也需要产品提供一定的“开”点。
  • 里氏替换原则(Liskov Substitution Principle):这个……和单元测试关系不大。
  • 接口分离原则(Interface Segregation Principle):只有通过接口和具体实现类分离之后,才能在测试时为接口提供Mock或Stub。例如,把职责提取到外部类的时候,我会为外部类构建一个接口。而原来类要使用外部类的功能,便是通过接口来访问的。
  • 依赖倒转原则(Dependency Inversion Principle):这个就不用说了,大大简化了单元测试的编写难度。值得注意的是,依赖注入不等同于“依赖注入容器”的使用。例如,我会为待测试的类添加一个用于注入辅助对象的构造函数,然后在单元测试时传入辅助对象的Stub。这其实也就是“依赖注入”。

在推特上“发布”我的TTD之后,有朋友告诉我说其实这也是TDD啊:Testability Driven Development,可测试性驱动开发。哎,真神奇。在下一篇文章中,我会使用一个简单的示例来展示“可测试性驱动开发”的实践方式,也希望能够引起更多更广泛的探讨。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

154 条回复

  1. Marlboro
    *.*.*.*
    链接

    Marlboro 2009-10-15 13:38:00

    学习了.

  2. 老赵
    admin
    链接

    老赵 2009-10-15 13:46:00

    @Marlboro
    别急着“学习”,这些是我的思考,是拿来讨论的。

  3. 包建强
    *.*.*.*
    链接

    包建强 2009-10-15 13:49:00

    你这篇文章真的有点扯蛋了,基本是发发牢骚,然后大概念套了一堆,一点技术含量都没有。
    之所以这么说,是因为我今天也写了一点在MVP中UnitTest的注意事项,也要涉及到TDD。但是我没发到首页上,因为我觉得自己列出的那10条,每一条都需要Sample支持。
    所以,你这篇文章杀了也罢,别忽悠初学者。

    还有上面那个Marlboro的回复,我很好奇,你学会了啥?

  4. 5207
    *.*.*.*
    链接

    5207 2009-10-15 13:55:00

    在实际项目中也是一直想加入单元测试,由于各种“懒”都没有好好做。至于TDD更是谈不上。

    老赵的经验很好,谢谢分享。

  5. 老赵
    admin
    链接

    老赵 2009-10-15 13:57:00

    @包建强
    本来就是经验总结,拿来探讨的,又没有说我这个就是对的,让别人直接学习。
    如果我不写出来,直接删了,拿什么和人讨论?我写博客是为了讨论不是为了当老师的。

    而且,我不是也写着在下一篇文章中,我会准备一个简单的示例么。
    我从不忽悠,如果我错了,那么就是我错了,人人都可以指出。
    我从没想要把错的东西说成对的。

  6. 熊呜呜
    *.*.*.*
    链接

    熊呜呜 2009-10-15 13:58:00

    通常我是先定义接口,然后写测试代码..最后写代码.

  7. 西狐
    *.*.*.*
    链接

    西狐 2009-10-15 13:58:00

    社会发展了.技术也在变

  8. 包建强
    *.*.*.*
    链接

    包建强 2009-10-15 14:02:00

    @Jeffrey Zhao
    我可没说你错了,因为你套的都是大概念,说你错了就意味着说SOLID错了。
    在没看到示例之前,你这篇就是水文。而且做TDD设计是要反复修改TestCase的,你写一个简单的Demo就能说清楚,那就见鬼了。

    所以,说你忽悠,还是轻判了,说是误导入门者,又罪不当诛。你这篇文章,就是沾染了那个虾米虾米的文风,华而不实罢了。

    我更奇怪的是,还有很多人留言,说看完此文学会了虾米虾米,这些人到底学会了啥?

  9. 寒星
    *.*.*.*
    链接

    寒星 2009-10-15 14:09:00

    一篇普通的博文而已,咋闻出火药味了。做技术的,还是淡定一点的好。好的东西吸收,糟粕无视不就好了。

  10. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-10-15 14:12:00

    其实我是很怀疑测试驱动开发到底能适用于哪些场景。如果测试用例已经写好,说明设计已经完备,这与敏捷的理念是背道而驰的。或者可以换成,测试的粒度到底在什么程度?如果一个方法只是负责a+b,在外面的测试用例测试其是不是完成了a+b岂不是很无聊?但任何功能都是这些无聊的小工作组成的。

  11. 老赵
    admin
    链接

    老赵 2009-10-15 14:13:00

    @包建强
    我错了关solid什么事,当然也有可能是我理解错了,解读错了。
    我的示例不是一段代码,而是一个开发,测试,重构的过程。
    别人展示TDD是怎么做的,难道不是通过示例吗?我也是通过这样的示例。
    不过你有一点没说错,在没看到示例之前,单独阅读这篇的确价值不大。

  12. 老赵
    admin
    链接

    老赵 2009-10-15 14:14:00

    @寒星
    好的东西吸收,但也要分辨,如果真是误导了初学者,指出来也没有问题。
    所以,包子目前的举动我是欢迎的,我也欢迎所有人来挑刺。
    写东西不就是让别人评价的么,如果只是“无视”,至少我不建议这么做。

  13. 老赵
    admin
    链接

    老赵 2009-10-15 14:16:00

    @Ivony...
    嗯,我文章里也写了,有时候觉得“这东西没必要这样写”,比较无聊。
    理论上说,TDD的意义便是“渐进式”的,一点点来。
    但是我还是接受不了,所以我只是考虑可测试性,然后每个测试都是有意义的。
    不会太大,否则测试难写。也不会太小,感觉没有意义。

  14. 寒星
    *.*.*.*
    链接

    寒星 2009-10-15 14:20:00

    @Jeffrey Zhao
    个人来说,比较喜欢安静的去读些东西,吸收自己觉得好的。不好的嘛,看情况,懒得说的一般都会无视掉。这年头喜欢喷的人比较多,时间久了,就有了无视的习惯了。

  15. 道法自然
    *.*.*.*
    链接

    道法自然 2009-10-15 14:20:00

    有同感。见过ThoughtWorks高手演示过TDD,但是我自己使用起来还真嫌麻烦。不过,如老赵所说,测试还是要保留,最终也是UnitTest。对于一个类库、组件或框架而言,并不是能够直接执行,即使能够直接运行的,也不能确保错误很少。因此,一般也会为每一个类或每一个功能编写一个UnitTest。UnitTest对于重构和质量保证来说,意义非凡,不过,我也认为我没有达到ThoughtWorks所说的TDD。

    当然,我们公司那帮高手也没有真正做到TW的TDD。

  16. 李永京
    *.*.*.*
    链接

    李永京 2009-10-15 14:21:00

    哈哈,老赵的这篇文章的确水了点,不过SOLID还是挺有用的~~体会到了。

  17. 邀月
    *.*.*.*
    链接

    邀月 2009-10-15 14:21:00

    我觉得老赵这篇应该放在首页候选区,参考老赵其它更多更好的作品而言。如果是我写的,我可能也放首页,老赵不同。
    跟其他任何人的观点无关。我是来学习的。

  18. Marlboro
    *.*.*.*
    链接

    Marlboro 2009-10-15 14:23:00

    @包建强
    刚学1年开发,什么东西都值得学习,于是乎我就学会了这个。


    单一职责原则(Single Resposibility Principle):如果一个类的职责不单一,我写单元测试的时候就要准备一个复杂的初始数据,然后劳心劳力地推测出它的输出是什么。此时,我会把一部分职责抽象成外部类,然后再某种方式交由原来的类使用。在单元测试的时候,我可以为新生成的外部类构造Stub,也可以为这个外部类做额外的单元测试。
    开/闭原则(Open/Close Principle):这个似乎和单元测试的关系不大,符合这个原则更多是为了更好的产品设计。当然,但愿测试本身也需要产品提供一定的“开”点。
    里氏替换原则(Liskov Substitution Principle):这个……和单元测试关系不大。
    接口分离原则(Interface Segregation Principle):只有通过接口和具体实现类分离之后,才能在测试时为接口提供Mock或Stub。例如,把职责提取到外部类的时候,我会为外部类构建一个接口。而原来类要使用外部类的功能,便是通过接口来访问的。
    依赖倒转原则(Dependency Inversion Principle):这个就不用说了,大大简化了单元测试的编写难度。值得注意的是,依赖注入不等同于“依赖注入容器”的使用。例如,我会为待测试的类添加一个用于注入辅助对象的构造函数,然后在单元测试时传入辅助对象的Stub。这其实也就是“依赖注入”。


    要学习的东西太多,只能学习了!这些东西可能对你来说比较熟悉.但是对我来说还不能深入理解!over~

  19. 老赵
    admin
    链接

    老赵 2009-10-15 14:26:00

    @邀月
    对了,这里打个招呼,除非真实无聊而写的文章,我的所有博客都会发到首页,否则实在没有人看啊。
    在这一点上我就脸皮厚一把了,呵呵。

  20. 道法自然
    *.*.*.*
    链接

    道法自然 2009-10-15 14:28:00

    老赵就是分享自己的困惑而已,我觉得并不是水,有什么可批判的吗。如果你觉得太水的话,那就说一下真正的TDD好了。

  21. Teddy's Knowledge Bas…
    *.*.*.*
    链接

    Teddy's Knowledge Base 2009-10-15 14:30:00

    我相信老赵平时写的代码,更多的偏向于framework或者至少是构架级别的代码,这种类型的代码往往并不适合传统概念的TDD,因为,这往往需要比较高的创造性和技巧性,在开发的过程中,也会随时大量重构甚至重新设计部分甚至整体的模块,并且往往接口都相对比较频繁地改动。这样一来,如果机械的采用TDD,那就只是最大化了TDD的缺点。

    个人的看法是TDD还是更适合那些系统的接口相对稳定,或者说,在系统的接口已经相对稳定之后再采用。不过,对某些高创造性和技巧性的组件的开发来说,可能到这个阶段的时候,已经是开发的中晚期了,那也就不算TDD了。从来没人说过,TDD适合一切,不过,绝对可以说unit test对大多数系统来说都是需要的。

  22. 紫色阴影
    *.*.*.*
    链接

    紫色阴影 2009-10-15 14:32:00

    漂亮的代码总是测试友好的,如果你发现写的代码不好测试,就证明写得烂

    做了这么久的TDD,反正我不同意测试能驱动设计,不管别人怎么说
    但是TDD能帮助我开发,小步前进,而且每一行代码都是测试驱动出来的,测试覆盖也上去了

  23. 包建强
    *.*.*.*
    链接

    包建强 2009-10-15 14:34:00

    道法自然:
    有同感。见过ThoughtWorks高手演示过TDD,但是我自己使用起来还真嫌麻烦。不过,如老赵所说,测试还是要保留,最终也是UnitTest。对于一个类库、组件或框架而言,并不是能够直接执行,即使能够直接运行的,也不能确保错误很少。因此,一般也会为每一个类或每一个功能编写一个UnitTest。UnitTest对于重构和质量保证来说,意义非凡,不过,我也认为我没有达到ThoughtWorks所说的TDD。

    当然,我们公司那帮高手也没有真正做到TW的TDD。



    别相信“ThoughtWorks高手演示过TDD”,我看过他的blog,对TDD的理解也是研究远多于实践,读书笔记多于项目心得。这话说出来肯定得罪人,但是得罪人我也要讲,真用过TDD的,应该是抱怨多于介绍,实战中的“旁门左道”多于循规蹈矩地写TestMethod。

    夸张地说,什么时候你对TDD抱有“苦大仇深”的感情,ok,就算得道成仙了。

  24. 紫色阴影
    *.*.*.*
    链接

    紫色阴影 2009-10-15 14:34:00

    园子里的都是朋友,怎么总是火药味?

  25. 期待中下[未注册用户]
    *.*.*.*
    链接

    期待中下[未注册用户] 2009-10-15 14:36:00

    期待中下篇。只要够精彩,这里提纲挈领的一篇在首页也算顺利成章。

  26. chy710
    *.*.*.*
    链接

    chy710 2009-10-15 14:37:00

    说实话,一般的中小企业很难做到TDD,思想及开发习惯接受不了,时间人力成本更接受不了!

  27. 老赵
    admin
    链接

    老赵 2009-10-15 14:40:00

    @chy710
    TDD的问题不是带来时间和人力成本,反而是减少。
    我说的TDD是我的TDD,或者说你理解为“单元测试”总是没错的。

  28. 老赵
    admin
    链接

    老赵 2009-10-15 14:40:00

    @Teddy's Knowledge Base
    但是,网上可以搜到不少TDD方面的大师认为,如果使用了单元测试,但是不用测试驱动开发,也还是一种偏差。
    // 想起来了,这话是Uncle Bob说的,我去找链接。

  29. 邀月
    *.*.*.*
    链接

    邀月 2009-10-15 14:40:00

    以前看老赵的一篇文章,好像大意是说在公交车上还是地铁上解决一个问题,在颠簸得吐出来这前把代码调试完了,大意是这样。当时我看的时候很感动,很有感触。但下面这句话

    Jeffrey Zhao:
    @邀月
    否则实在没有人看啊。


    反映了老赵跟我们常人一样的顽童心理,或者说也对寂寞怀有恐惧?

  30. 老赵
    admin
    链接

    老赵 2009-10-15 14:41:00

    @期待中下
    目前计划中只有下,没有中下,呵呵。

  31. 1497[未注册用户]
    *.*.*.*
    链接

    1497[未注册用户] 2009-10-15 14:46:00

    包建强:
    你这篇文章真的有点扯蛋了,基本是发发牢骚,然后大概念套了一堆,一点技术含量都没有。
    之所以这么说,是因为我今天也写了一点在MVP中UnitTest的注意事项,也要涉及到TDD。但是我没发到首页上,因为我觉得自己列出的那10条,每一条都需要Sample支持。
    所以,你这篇文章杀了也罢,别忽悠初学者。

    还有上面那个Marlboro的回复,我很好奇,你学会了啥?


    又见愤青出场.

  32. 紫色阴影
    *.*.*.*
    链接

    紫色阴影 2009-10-15 14:46:00

    Teddy's Knowledge Base:
    我相信老赵平时写的代码,更多的偏向于framework或者至少是构架级别的代码,这种类型的代码往往并不适合传统概念的TDD,因为,这往往需要比较高的创造性和技巧性,在开发的过程中,也会随时大量重构甚至重新设计部分甚至整体的模块,并且往往接口都相对比较频繁地改动。这样一来,如果机械的采用TDD,那就只是最大化了TDD的缺点。

    个人的看法是TDD还是更适合那些系统的接口相对稳定,或者说,在系统的接口已经相对稳定之后再采用。不过,对某些高创造性和技巧性的组件的开发来说,可能到这个阶段的时候,已经是开发的中晚期了,那也就不算TDD了。从来没人说过,TDD适合一切,不过,绝对可以说unit test对大多数系统来说都是需要的。



    如果你写了一个组件,然后写它的unit test。当需要重构或者重新设计, unit test难道不需要改动?
    这一点不能作为理由,不过有些场景TDD的确是没法做,这个有感受


  33. 紫色阴影
    *.*.*.*
    链接

    紫色阴影 2009-10-15 14:48:00

    邀月:
    以前看老赵的一篇文章,好像大意是说在公交车上还是地铁上解决一个问题,在颠簸得吐出来这前把代码调试完了,大意是这样。当时我看的时候很感动,很有感触。但下面这句话

    Jeffrey Zhao:
    @邀月
    否则实在没有人看啊。


    反映了老赵跟我们常人一样的顽童心理,或者说也对寂寞怀有恐惧?



    老赵比较可爱嘛

  34. 老赵
    admin
    链接

    老赵 2009-10-15 14:48:00

    @邀月
    我写文章是为了讨论,如果不希望被人看到写个乜呀,呵呵。

  35. Marlboro
    *.*.*.*
    链接

    Marlboro 2009-10-15 14:49:00

    1497:

    包建强:
    你这篇文章真的有点扯蛋了,基本是发发牢骚,然后大概念套了一堆,一点技术含量都没有。
    之所以这么说,是因为我今天也写了一点在MVP中UnitTest的注意事项,也要涉及到TDD。但是我没发到首页上,因为我觉得自己列出的那10条,每一条都需要Sample支持。
    所以,你这篇文章杀了也罢,别忽悠初学者。

    还有上面那个Marlboro的回复,我很好奇,你学会了啥?


    又见愤青出场.




    我刚注册了博客园,刚看了东西感觉对自己很有用.就学习了!
    第一次留言啊.幼小的心灵就被打击了。。

  36. 别害怕[未注册用户]
    *.*.*.*
    链接

    别害怕[未注册用户] 2009-10-15 14:57:00

    @Marlboro

    Marlboro:

    1497:

    包建强:
    你这篇文章真的有点扯蛋了,基本是发发牢骚,然后大概念套了一堆,一点技术含量都没有。
    之所以这么说,是因为我今天也写了一点在MVP中UnitTest的注意事项,也要涉及到TDD。但是我没发到首页上,因为我觉得自己列出的那10条,每一条都需要Sample支持。
    所以,你这篇文章杀了也罢,别忽悠初学者。

    还有上面那个Marlboro的回复,我很好奇,你学会了啥?


    又见愤青出场.




    我刚注册了博客园,刚看了东西感觉对自己很有用.就学习了!
    第一次留言啊.幼小的心灵就被打击了。。


    好可怜,Marlboro(马可波罗)。别灰心,园子里这样人少。

  37. 紫色阴影
    *.*.*.*
    链接

    紫色阴影 2009-10-15 14:57:00

    包建强:

    道法自然:
    有同感。见过ThoughtWorks高手演示过TDD,但是我自己使用起来还真嫌麻烦。不过,如老赵所说,测试还是要保留,最终也是UnitTest。对于一个类库、组件或框架而言,并不是能够直接执行,即使能够直接运行的,也不能确保错误很少。因此,一般也会为每一个类或每一个功能编写一个UnitTest。UnitTest对于重构和质量保证来说,意义非凡,不过,我也认为我没有达到ThoughtWorks所说的TDD。

    当然,我们公司那帮高手也没有真正做到TW的TDD。



    别相信“ThoughtWorks高手演示过TDD”,我看过他的blog,对TDD的理解也是研究远多于实践,读书笔记多于项目心得。这话说出来肯定得罪人,但是得罪人我也要讲,真用过TDD的,应该是抱怨多于介绍,实战中的“旁门左道”多于循规蹈矩地写TestMethod。

    夸张地说,什么时候你对TDD抱有“苦大仇深”的感情,ok,就算得道成仙了。



    如果你做过1年以上的TDD,然后抱怨如何如何不好,嗯,有实践有理有据,还能够让别人思考一下。
    如果你只是自己先写了测试,然后写代码,就说这样真麻烦啊,TDD真差劲。那你就是瞎嚷嚷,没人会听

  38. lau
    *.*.*.*
    链接

    lau 2009-10-15 15:01:00

    个人感觉有点不太显示,就现在的环境来讲。
    没有多少人会花费如此大的时间成本去做。

  39. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-10-15 15:02:00

    我觉得TDD适用于那些算法性的需求,譬如说我们需要做一个东西在几个数据表中保持数据一致性,这种东西的测试用例就可以提前做出来,并且在确保我们在重构的时候不会出错。

    但是对于我所遇到的大部分需求,TDD似乎都是无聊的代名词。

  40. 紫色阴影
    *.*.*.*
    链接

    紫色阴影 2009-10-15 15:05:00

    @Ivony...
    大哥,测试用例的测试和TDD的测试不是一个东西

  41. 邀月
    *.*.*.*
    链接

    邀月 2009-10-15 15:05:00

    Jeffrey Zhao:
    @邀月
    我写文章是为了讨论,如果不希望被人看到写个乜呀,呵呵。


    这话我不完全赞成。
    理由:
    知识在传播的过程中产生了价值。
    知识在快速而广泛的传播过程中产生了更大的价值。
    负面知识在快速而广泛的传播过程中产生了更大的负面价值。
    我写东东有些是给自己总结用的,多年后自己回头看看,对自己是个总结,以间写纸质日记,现在是电子的方式。还想将来退休后好写回忆录养老呢。
    当然我也想被人看,但我水平远远不够,就在小范围传播好了,即使错了,也影响小部分的人。当然尽可能地少错。
    所以,我认为:老赵是“想被更多的人看到”,是一种想快速传播分享的心态。否则你不放首页还是会被好多人看到的,google帮你解决

  42. Teddy's Knowledge Bas…
    *.*.*.*
    链接

    Teddy's Knowledge Base 2009-10-15 15:06:00

    紫色阴影:

    Teddy's Knowledge Base:
    我相信老赵平时写的代码,更多的偏向于framework或者至少是构架级别的代码,这种类型的代码往往并不适合传统概念的TDD,因为,这往往需要比较高的创造性和技巧性,在开发的过程中,也会随时大量重构甚至重新设计部分甚至整体的模块,并且往往接口都相对比较频繁地改动。这样一来,如果机械的采用TDD,那就只是最大化了TDD的缺点。

    个人的看法是TDD还是更适合那些系统的接口相对稳定,或者说,在系统的接口已经相对稳定之后再采用。不过,对某些高创造性和技巧性的组件的开发来说,可能到这个阶段的时候,已经是开发的中晚期了,那也就不算TDD了。从来没人说过,TDD适合一切,不过,绝对可以说unit test对大多数系统来说都是需要的。



    如果你写了一个组件,然后写它的unit test。当需要重构或者重新设计, unit test难道不需要改动?
    这一点不能作为理由,不过有些场景TDD的确是没法做,这个有感受




    TDD和写了一个组件,然后写unit test,开发方式上还是有很大的不同的。如果不采用TDD,我的unittest一开始会比较general,甚至只整体测试一些核心功能。这样,重构组件时时,重构unittest的成本也会低得多。等组件相对稳定以后,再加上覆盖更全的测试,这样不是经济得多吗?

    但如果是TDD,我总不可能也是先写所谓general的测试(否则还叫TDD吗?),我得基于接口,一开始就写较全的测试,那样重构成本要远远大得多。

  43. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-10-15 15:11:00

    紫色阴影:
    @Ivony...
    大哥,测试用例的测试和TDD的测试不是一个东西



    迷惑,
    那么TDD的Test到底是怎么去实践的呢?总不能是一个测试团队跟在后面程序员每Release一个版本也不管什么东西改了就全面的测一次吧

  44. 紫色阴影
    *.*.*.*
    链接

    紫色阴影 2009-10-15 15:11:00

    Teddy's Knowledge Base:

    紫色阴影:

    Teddy's Knowledge Base:
    我相信老赵平时写的代码,更多的偏向于framework或者至少是构架级别的代码,这种类型的代码往往并不适合传统概念的TDD,因为,这往往需要比较高的创造性和技巧性,在开发的过程中,也会随时大量重构甚至重新设计部分甚至整体的模块,并且往往接口都相对比较频繁地改动。这样一来,如果机械的采用TDD,那就只是最大化了TDD的缺点。

    个人的看法是TDD还是更适合那些系统的接口相对稳定,或者说,在系统的接口已经相对稳定之后再采用。不过,对某些高创造性和技巧性的组件的开发...



    等代码freeze了以后,那时再加上所有的测试,写完就不用再修改,难道是最经济的做法吗?

  45. 包建强
    *.*.*.*
    链接

    包建强 2009-10-15 15:11:00

    Ivony...:
    我觉得TDD适用于那些算法性的需求,譬如说我们需要做一个东西在几个数据表中保持数据一致性,这种东西的测试用例就可以提前做出来,并且在确保我们在重构的时候不会出错。

    但是对于我所遇到的大部分需求,TDD似乎都是无聊的代名词。



    你要学会去接受新事物,然后再批判的继承,即使觉得不好也要动手实践一下,切身体会使用的心得,就算是失败了也不要在乎。这才是做软件的必备素质。
    狭隘地不接受思想,比如前两天有人嚷嚷连接口继承组合都不会照样写程序做软件,那只能成为笑柄,滑天下之大稽。

  46. Teddy's Knowledge Bas…
    *.*.*.*
    链接

    Teddy's Knowledge Base 2009-10-15 15:19:00

    @紫色阴影

    紫色阴影:

    Teddy's Knowledge Base:

    紫色阴影:

    Teddy's Knowledge Base:
    我相信老赵平时写的代码,更多的偏向于framework或者至少是构架级别的代码,这种类型的代码往往并不适合传统概念的TDD,因为,这往往需要比较高的创造性和技巧性,在开发的过程中,也会随时大量重构甚至重新设计部分甚至整体的模块,并且往往接口都相对比较频繁地改动。这样一来,如果机械的采用TDD,那就只是最大化了TDD的缺点。

    个人的看法是TDD还是更适合那些系统的接口相对稳定,或者说,在系统的接口已经相对稳定之后再采用。不过,对某些高创造性和技巧性的组件的开发...



    等代码freeze了以后,那时再加上所有的测试,写完就不用再修改,难道是最经济的做法吗?


    我并非这个意思,我说上面这些的前提是,对某些高创造性和技巧性的组件来说,初期的开发,重构会很频繁,一上来就TDD绝对不经济。但是,在相对稳定以后,完善了测试以后,以后的维护过程,本质上也算是TDD,也是适合TDD的,因为不太会有太频繁和较大的重构。

    归根结底,做任何事我们不要太机械,理论总有它适合的场景,但是,我们做事很多时候要求一个balance。最大化这些理论的最佳场景下的好处,同时尽可能的规避它在不太适合的场景下的缺点。

  47. 紫色阴影
    *.*.*.*
    链接

    紫色阴影 2009-10-15 15:19:00

    Ivony...:

    紫色阴影:
    @Ivony...
    大哥,测试用例的测试和TDD的测试不是一个东西



    迷惑,
    那么TDD的Test到底是怎么去实践的呢?总不能是一个测试团队跟在后面程序员每Release一个版本也不管什么东西改了就全面的测一次吧



    TDD的测试是开发人员写的。比如我要实现一个功能,将实现的过程分为N个步骤(Baby step),每一步达到一个小而细的目标。开始实现每一步之前,先写一个测试用来验证我这一步是否做得对,再写产品代码实现。如果测试通过,这一步就算完成。直到所有步骤做完,功能实现。

    所以是驱动开发

  48. 林肯[未注册用户]
    *.*.*.*
    链接

    林肯[未注册用户] 2009-10-15 15:23:00

    @Ivony...

    Ivony...:如果测试用例已经写好,说明设计已经完备,这与敏捷的理念是背道而驰的。


    不知道您所指的"与敏捷的理念是背道而驰的"所指的是那个理念?

  49. 紫色阴影
    *.*.*.*
    链接

    紫色阴影 2009-10-15 15:25:00

    @Teddy's Knowledge Base

    很赞同
    任何实践、模式都有适用的context,一概而论是不对的

  50. 包建强
    *.*.*.*
    链接

    包建强 2009-10-15 15:32:00

    @Ivory
    不是我打击您哦,您对开发人员写的UT和测试人员作的Test还没分清楚。当然,对TDD的流程也不了解了,进而上升到agile。
    建议去买本TDD的书,从头做一遍。

    @紫色阴影
    看完你的feedback,感觉你是备受TDD的折磨了,但只是被动的接受,所谓“被TDD”了,还没有感受到它的好处。
    建议你找个雨天,去咖啡屋泡杯茶,欣赏着雨景,然后反思一下“被TDD”的成败得失,你还需要把开闭原则也考虑在内。
    主动还是被动不取决于你,也许哪天你做Leader了,也会大力推行这套开发流程的。

  51. 紫色阴影
    *.*.*.*
    链接

    紫色阴影 2009-10-15 15:44:00

    包建强:
    @Ivory
    不是我打击您哦,您对开发人员写的UT和测试人员作的Test还没分清楚。当然,对TDD的流程也不了解了,进而上升到agile。
    建议去买本TDD的书,从头做一遍。

    @紫色阴影
    看完你的feedback,感觉你是备受TDD的折磨了,但只是被动的接受,所谓“被TDD”了,还没有感受到它的好处。
    建议你找个雨天,去咖啡屋泡杯茶,欣赏着雨景,然后反思一下“被TDD”的成败得失,你还需要把开闭原则也考虑在内。
    主动还是被动不取决于你,也许哪天你做Leader了,也会大力推行这套开发流程的。



    不好意思,TDD并没有折磨我,而是相见恨晚,现在我正在推行TDD,效果好与坏得慢慢看
    不用你操心,该思考的早也思考过了。它不能解决你的问题但是可以帮助我,你一定认为TDD就是垃圾我也没有话可说

  52. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-10-15 15:47:00

    林肯:
    @Ivony...

    Ivony...:如果测试用例已经写好,说明设计已经完备,这与敏捷的理念是背道而驰的。


    不知道您所指的"与敏捷的理念是背道而驰的"所指的是那个理念?



    以下摘自敏捷宣言:
    对我们而言,最重要的是通过尽早和不断交付有价值的软件满足客户需要。
    经常交付可以工作的软件,从几星期到几个月,时间尺度越短越好。
    简单——尽可能减少工作量的艺术至关重要。



    试想,一个完备的设计在几个星期到几个月里面都做出不来,又怎么可能交付可以工作的软件?

  53. 包建强
    *.*.*.*
    链接

    包建强 2009-10-15 15:49:00

    紫色阴影:

    包建强:
    @Ivory
    不是我打击您哦,您对开发人员写的UT和测试人员作的Test还没分清楚。当然,对TDD的流程也不了解了,进而上升到agile。
    建议去买本TDD的书,从头做一遍。

    @紫色阴影
    看完你的feedback,感觉你是备受TDD的折磨了,但只是被动的接受,所谓“被TDD”了,还没有感受到它的好处。
    建议你找个雨天,去咖啡屋泡杯茶,欣赏着雨景,然后反思一下“被TDD”的成败得失,你还需要把开闭原则也考虑在内。
    主动还是被动不取决于你,也许哪天你做Leader了,也会大力推行这套开发流程的。



    不好意思,TDD并没有折磨我,而是相见恨晚,现在我正在推行TDD,效果好与坏得慢慢看
    不用你操心,该思考的早也思考过了。它不能解决你的问题但是可以帮助我,你一定认为TDD就是垃圾我也没有话可说



    我什么时候说TDD是垃圾了。
    我只是告诉你,既然你使用了TDD,那么就不应该存在设计组件而接口和方法经常变动的情况,你应该把开闭原则也包括进来,至于你听不听,或者是否误解了我的意思,那是你的事情。

  54. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-10-15 15:50:00

    我觉得放首页完全可以啊?

    不过观点我完全不认同:
    第一点 老赵提出的所谓可测这个概念比较暧昧,对于一个测试工程师而言,没有可测不可测只有代价大小

    第二点 只有单元测试是依赖设计的 测试应该根据需求来确定而非设计 所以测试与设计应该是不相关的 根据测试改进设计的说法就很奇怪了

    感觉老赵所谓的测试多半是特指单元测试,跟TDD的测试区别还是很大的。

    最后提一下 所谓测试驱动的核心思想,就是用比较细致的Test case代替概括性的Use case来描述需求,是把开发的目标由“实现需求”变成更明确和容易执行的“通过测试” 测试驱动开发不一定是自动化测试

    TDD的测试不应该是开发人员写的 除了单元测试 开发人员不应该写任何测试用例

    所以楼上有些同学不要乱说乱捧 我想老赵也不喜欢这样

  55. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-10-15 15:54:00

    忍不住水一贴
    "去咖啡屋泡杯茶"这句超赞
    我想去星巴克冲杯雀巢......

  56. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-10-15 15:56:00

    紫色阴影:

    Ivony...:

    紫色阴影:
    @Ivony...
    大哥,测试用例的测试和TDD的测试不是一个东西



    迷惑,
    那么TDD的Test到底是怎么去实践的呢?总不能是一个测试团队跟在后面程序员每Release一个版本也不管什么东西改了就全面的测一次吧



    TDD的测试是开发人员写的。比如我要实现一个功能,将实现的过程分为N个步骤(Baby step),每一步达到一个小而细的目标。开始实现每一步之前,先写一个测试用来验证我这一步是否做得对,再写产品代码实现。如果测试通过,这一步就算完成。直到所有步骤做完,功能实现。

    所以是驱动开发




    我觉得这恰恰与敏捷的思想背道而驰。这种将问题分解成多个小问题,再层层分解实现的方法,恰恰是瀑布模型或是自顶向下的思维模式。

    简单的说,由于每一个步骤都是为下一个步骤准备。如果最终的步骤发生改变(需求变化)则前面的步骤都会受到不同程度的影响。

    或者说这种工作模式不可能在短时间内交付可以工作的软件。


    我对TDD的理解是,应该将需求变为测试,利用测试来验证是否达到需求而不是利用是否完成设计作为指标。程序的目的是通过测试而不是满足设计。在通过测试这个最大前提下,我们优化和重构程序逻辑和代码。
    或者说测试驱动开发的反面是设计驱动开发。


    而敏捷所倚重的迭代开发模式,恰恰不是将问题分解成若干小问题,而是尽早交付最简单的版本,通过迭代完成最终的成品。理念是我们永远不可能知道客户想要解决什么问题,只能解决当下所提出来的问题,然后再聆听客户的反馈。每一次迭代都是对前一次的修正而不是前一次的功能是预设为后面做的准备。


    换言之敏捷要求你的程序员以完成整个车为目标而不是车轱辘。就算第一次的成品是辆独轮板车,这并不妨碍在N次迭代后它会变成宝马奔驰。

  57. 林肯[未注册用户]
    *.*.*.*
    链接

    林肯[未注册用户] 2009-10-15 15:58:00

    Teddy's Knowledge Base:
    我相信老赵平时写的代码,更多的偏向于framework或者至少是构架级别的代码,这种类型的代码往往并不适合传统概念的TDD,因为,这往往需要比较高的创造性和技巧性,在开发的过程中,也会随时大量重构甚至重新设计部分甚至整体的模块,并且往往接口都相对比较频繁地改动。这样一来,如果机械的采用TDD,那就只是最大化了TDD的缺点。

    个人的看法是TDD还是更适合那些系统的接口相对稳定,或者说,在系统的接口已经相对稳定之后再采用。不过,对某些高创造性和技巧性的组件的开发来说,可能到这个阶段的时候,已经是开发的中晚期了,那也就不算TDD了。从来没人说过,TDD适合一切,不过,绝对可以说unit test对大多数系统来说都是需要的。



    为什么您认为架构级的代码不适合TDD? 我觉得越是架构级的代码,越需要TDD.
    1) Simplicity is the prerequisite of reliability --Edsger W.Dijkstra
    我同意架构级的代码需要创造性和技巧性, 但是TDD强调的用最少的代码去通过当前测试,它可以保证你的代码尽量的简单, simple and simple enough.
    2)您说"随时大量重构甚至重新设计部分甚至整体的模块,并且往往接口都相对比较频繁地改动"
    我觉得越是频繁的重构和改动,越需要TDD建立起来的unit test来给你做出的架构级调整以信心.
    3)您说 "这样一来,如果机械的采用TDD,那就只是最大化了TDD的缺点."
    我觉得正式这样才最大化了TDD的优点. TDD建立的unit test, 它的价值就体现在频繁反复的跑测试的过程中, 帮你尽早的尽量多的发现你改动的代码的缺陷.如果单元测试永远在你写完实现之后写出来,还仅仅跑那么一次两次,那它就没有价值.
    4)您说 "个人的看法是TDD还是更适合那些系统的接口相对稳定...."
    我觉得不然. 假如我没有理解错的话, 您似乎不喜欢修改测试, 觉得接口相对稳定, 测试就不需要频繁修改. 我觉得TDD不仅发生在实现一些功能的时候, 它也发生在修改任何功能的时候. 当有功能变化的时候, 只有修改现有测试,使他能完备的体现新的需求的时候, 你做出的代码修改,才会尽量的不多不少,刚刚好.

    "从来没人说过,TDD适合一切,不过,绝对可以说unit test对大多数系统来说都是需要的。" 我非常赞同.

  58. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-10-15 16:02:00

    @林肯
    TDD的确是不能适用于所有的场景。


    所以我最想讨论和知道的是TDD适用的场景是哪些?为什么我们所遇到的场景中看起来TDD都是不太适用的。

  59. 紫色阴影
    *.*.*.*
    链接

    紫色阴影 2009-10-15 16:02:00

    包建强:

    紫色阴影:

    包建强:
    @Ivory
    不是我打击您哦,您对开发人员写的UT和测试人员作的Test还没分清楚。当然,对TDD的流程也不了解了,进而上升到agile。
    建议去买本TDD的书,从头做一遍。

    @紫色阴影
    看完你的feedback,感觉你是备受TDD的折磨了,但只是被动的接受,所谓“被TDD”了,还没有感受到它的好处。
    建议你找个雨天,去咖啡屋泡杯茶,欣赏着雨景,然后反思一下“被TDD”的成败得失,你还需要把开闭原则也考虑在内。
    主动还是被动不取决于你,也许哪天你做Leader了,也会大力推行这套开发流程的。



    不好意思,TDD并没有折磨我,而是相见恨晚,现在我正在推行TDD,效果好与坏得慢慢看
    不用你操心,该思考的早也思考过了。它不能解决你的问题但是可以帮助我,你一定认为TDD就是垃圾我也没有话可说



    我什么时候说TDD是垃圾了。
    我只是告诉你,既然你使用了TDD,那么就不应该存在设计组件而接口和方法经常变动的情况,你应该把开闭原则也包括进来,至于你听不听,或者是否误解了我的意思,那是你的事情。



    做敏捷的人都是实效的,不会为了什么而什么
    就像不会为了TDD而TDD一样,有的时候即使接口和方法经常变动,TDD也能处理得很好
    Teddy的回复不错,你还是带着学习的态度去看下吧

  60. 紫色阴影
    *.*.*.*
    链接

    紫色阴影 2009-10-15 16:05:00

    winter-cn:
    我觉得放首页完全可以啊?

    不过观点我完全不认同:
    第一点 老赵提出的所谓可测这个概念比较暧昧,对于一个测试工程师而言,没有可测不可测只有代价大小

    第二点 只有单元测试是依赖设计的 测试应该根据需求来确定而非设计 所以测试与设计应该是不相关的 根据测试改进设计的说法就很奇怪了

    感觉老赵所谓的测试多半是特指单元测试,跟TDD的测试区别还是很大的。

    最后提一下 所谓测试驱动的核心思想,就是用比较细致的Test case代替概括性的Use case来描述需求,是把开发的目标由“实现需求”变成更明确和容易执行的“通过测试” 测试驱动开发不一定是自动化测试

    TDD的测试不应该是开发人员写的 除了单元测试 开发人员不应该写任何测试用例

    所以楼上有些同学不要乱说乱捧 我想老赵也不喜欢这样



    你需要恶补一下知识,有时间去看Kent beck的测试驱动开发

  61. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-10-15 16:08:00

    我觉得很多时候会陷入一个怪圈,就是每个人都强调自己的理解是最符合某个东西的定义的。但实际上争论这个是一件更无聊的事情。无论这个定义是什么级别的大师所给出的,我们所要讨论的问题应该是怎样做最好,而不是怎样做是符合某个定义的。

    包括老赵所提出的的SOLID原则。。。。

  62. 紫色阴影
    *.*.*.*
    链接

    紫色阴影 2009-10-15 16:11:00

    Ivony...:
    我觉得很多时候会陷入一个怪圈,就是每个人都强调自己的理解是最符合某个东西的定义的。但实际上争论这个是一件更无聊的事情。无论这个定义是什么级别的大师所给出的,我们所要讨论的问题应该是怎样做最好,而不是怎样做是符合某个定义的。

    包括老赵所提出的的SOLID原则。。。。




    因为有些事情,有些人会想当然说它不好,而没有经过实践去验证到底好不好。
    有些人实践后觉得很好,但是这些人在一旁说风凉话

  63. 老赵
    admin
    链接

    老赵 2009-10-15 16:12:00

    winter-cn:
    我觉得放首页完全可以啊?

    不过观点我完全不认同:
    第一点 老赵提出的所谓可测这个概念比较暧昧,对于一个测试工程师而言,没有可测不可测只有代价大小

    第二点 只有单元测试是依赖设计的 测试应该根据需求来确定而非设计 所以测试与设计应该是不相关的 根据测试改进设计的说法就很奇怪了

    感觉老赵所谓的测试多半是特指单元测试,跟TDD的测试区别还是很大的。

    最后提一下 所谓测试驱动的核心思想,就是用比较细致的Test case代替概括性的Use case来描述需求,是把开发的目标由“实现需求”变成更明确和容易执行的“通过测试” 测试驱动开发不一定是自动化测试

    TDD的测试不应该是开发人员写的 除了单元测试 开发人员不应该写任何测试用例

    所以楼上有些同学不要乱说乱捧 我想老赵也不喜欢这样


    我说的可测试性都是对开发人员说的,测试就单是指单元测试。
    “可测试性”不单指“可测”和“不可测”,有难度之分,是个度量。
    我说的测试就是单元测试,TDD不也是单元测试吗?
    TDD的测试本就是开发人员写的,就是指单元测试,这个定义没有啥可以讨论的吧……

  64. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-10-15 16:15:00

    @Jeffrey Zhao

    老实说我也觉得应该不是单元测试。


    换言之Unit Test怎么能驱动Development呢?

    http://zh.wikipedia.org/wiki/%E6%B5%8B%E8%AF%95%E9%A9%B1%E5%8A%A8%E5%BC%80%E5%8F%91

    在维基百科上找到的定义比较符合我的理解。包括那些负面评价,应该说有相当的就是我所遇到的。

  65. 包建强
    *.*.*.*
    链接

    包建强 2009-10-15 16:16:00

    不计个人恩怨,虽然紫色阴影一直在指责我,但我还是赞他真正做过TDD,属于肚子里有货的那种。

  66. 路过2009[未注册用户]
    *.*.*.*
    链接

    路过2009[未注册用户] 2009-10-15 16:17:00

    如果包包和老赵在同一个公司,同一个项目会怎么样???
    猜想中。。。
    哈哈

  67. 林肯[未注册用户]
    *.*.*.*
    链接

    林肯[未注册用户] 2009-10-15 16:19:00

    winter-cn:
    我觉得放首页完全可以啊?

    不过观点我完全不认同:
    第一点 老赵提出的所谓可测这个概念比较暧昧,对于一个测试工程师而言,没有可测不可测只有代价大小

    第二点 只有单元测试是依赖设计的 测试应该根据需求来确定而非设计 所以测试与设计应该是不相关的 根据测试改进设计的说法就很奇怪了

    感觉老赵所谓的测试多半是特指单元测试,跟TDD的测试区别还是很大的。

    最后提一下 所谓测试驱动的核心思想,就是用比较细致的Test case代替概括性的Use case来描述需求,是把开发的目标由“实现需求”变成更明确和容易执行的“通过测试” 测试驱动开发不一定是自动化测试

    TDD的测试不应该是开发人员写的 除了单元测试 开发人员不应该写任何测试用例

    所以楼上有些同学不要乱说乱捧 我想老赵也不喜欢这样


    这位老兄你真的先了解一下TDD再来吧

  68. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-10-15 16:22:00

    @Jeffrey Zhao
    测试驱动不是单元测试驱动啊
    Test case肯定是直接来自feature

  69. 紫色阴影
    *.*.*.*
    链接

    紫色阴影 2009-10-15 16:23:00

    @Ivony...
    大哥,不要觉得和应该啊
    你去看看kent beck的书先

    话说你真是以前叱咤csdn的Ivony吗?

  70. 老赵
    admin
    链接

    老赵 2009-10-15 16:23:00

    Ivony...:
    @Jeffrey Zhao
    老实说我也觉得应该不是单元测试。
    换言之Unit Test怎么能驱动Development呢?
    http://zh.wikipedia.org/wiki/%E6%B5%8B%E8%AF%95%E9%A9%B1%E5%8A%A8%E5%BC%80%E5%8F%91
    在维基百科上找到的定义比较符合我的理解。包括那些负面评价,应该说有相当的就是我所遇到的。


    按照传统思维,的确应该先写产品代码,再写测试的。
    TDD就是一种逆向做法,先写测试,再写代码。
    我的做法也差不多,就是为了能够写好测试,让代码去迎合测试需求。
    因为我认为,这么做最后得到的结果,就是好的产品代码——例如,它符合solid原则。
    当然,写好代码的条件有很多,TDD也只是一个手段而已,提高成功的可能性,不是“银弹”。

  71. 紫色阴影
    *.*.*.*
    链接

    紫色阴影 2009-10-15 16:24:00

    @包建强
    汗 PK归PK,我跟你好像没有恩怨吧

  72. 妖居
    *.*.*.*
    链接

    妖居 2009-10-15 16:25:00

    感觉如果没有实际在项目中用过,或者没有试图在项目中用过单元测试的人,看这篇文章确实没什么用,因为老赵说的都是感受。别人不知道,我是确有同感的。毕竟我们拿到的需求是一个整体,自然就开始有设计的想法了。完全的TDD或者TDR确实很难实现。我现在的原则就是尽量的让代码可测试,并且对我比较没有把握的部分做测试。一点一点来,推行单元测试是一个漫长而艰巨的过程。

  73. Teddy's Knowledge Bas…
    *.*.*.*
    链接

    Teddy's Knowledge Base 2009-10-15 16:26:00

    @林肯

    其实我从来没说过,TDD对项目质量不好,相反,如果成本允许,为了自始至终保证代码的质量,即使高创造性的组件,一开始就TDD也未尝不可,我说的是一开始就这样做不经济,我说的更合理的方法,正是在不影响代码质量的前提下,试图去尽可能的降低这里的成本。事实上,构架级别的代码当他被别的程序员真正开始使用的时候,它实际上已经到了对外接口相对稳定的时候,此时,即使采用我说的渐进的方式,也已经应该有较完备的测试了,那么后续的开发,还是TDD的。

  74. 老赵
    admin
    链接

    老赵 2009-10-15 16:26:00

    @winter-cn
    测试驱动开发,它的测试就是单元测试,不是来源于产品feature,来源于被测试者的目的。
    例如“Calculator”的Add操作,它不是产品的feature,它是产品的螺丝钉。
    测试驱动开发,就是为这个螺丝钉的目的先写测试,再写这个螺丝钉。

  75. 温景良(Jason)
    *.*.*.*
    链接

    温景良(Jason) 2009-10-15 16:29:00

    "习惯在拿到一个需求的时候,在脑海里率先出现设计".
    这大概是很多开发者的第一反应吧,反正我是.

  76. 猪里爷[未注册用户]
    *.*.*.*
    链接

    猪里爷[未注册用户] 2009-10-15 16:41:00

    如果抛弃了某些测试,接口的变更是否影响其他。这点能否保证?可能就意味着更高的成本了。

  77. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-10-15 16:42:00

    紫色阴影:
    @Ivony...
    大哥,不要觉得和应该啊
    你去看看kent beck的书先

    话说你真是以前叱咤csdn的Ivony吗?




    Ivony只此一家,别无分号。。


    只不过这几天病鸟,脑子木有那么好使了。。。。


    不过“叱咤”CSDN的Ivony也从来不喜欢引经据典的,哈,,,


    我承认我的确不了解TDD,但我了解敏捷。

    以下纯属个人推测,说错勿怪:

    已知:
    TDD是XP所倚重的模式。
    XP是敏捷的。
    XP是迭代的。

    所以TDD必然也是敏捷的而且适用于迭代的。


    又已知:
    敏捷的理念是“尽可能快地交付可使用的版本”、“简单,尽可能的减少冗余流程和工作量”。
    XP的迭代周期不可能很长。

    那么TDD的Test岂不是很明显?
    即不可能是对某一个功能点的测试而是对整个原型的测试。
    或者说连老赵都觉得无聊的测试,又怎么会不是“冗余的流程”呢。

    我们还能从敏捷原则中找到:
    “可以工作的软件是进度的主要度量标准”
    而不是可工作的螺丝钉。
    换言之无论这个螺丝钉是否通过了测试并不能代表这个软件是可工作的。
    敏捷宣言中也提到“可以工作的软件重于求全责备的文档”
    同样不是可以工作的螺丝钉。

    如果说测试不是针对整个原型的测试,那么这个测试就不可能确保我们的目标:“可以工作的软件”。又怎么可能来驱动开发呢?

    既然我们开发的目标是可以工作的软件(而不是功能完备的软件,这是敏捷的特点)。那么我们需要驱动的是一个完成这个目标的开发过程,而不是其中的一部分。否则所有的这些部分组装成最终的可以工作的软件又是什么驱动的呢?


    好吧,我该买书了。。。。

  78. Jerry Chou
    *.*.*.*
    链接

    Jerry Chou 2009-10-15 16:49:00

    根据个人使用的TDD情况来看:
    确实有些鸡肋的感觉,因为实践中并没有使用Beck所说的那种Step。

    测试驱动开发不一定是自动化测试
    对于这句,我想说的是:不是自动化的测试——是没有愿意写的测试。

    希望有一天在VS可以一边写代码一边自动化测试,当我一行代码完成后,测试结果就会出来,这样或许可以激发写测试的积极性。

    在实践编程作业中,在有些Context中TDD似乎不实用(比如理解业务时,UI设计时),而Context切换太快,以至于有时感觉TDD很好走,有时又不好走,所以久之便抛弃了。(还是说到自动化上了——如果只在编码/设计这一个环节上,使用TDD加上完全自动的测试——应该很爽吧~)

  79. 林肯[未注册用户]
    *.*.*.*
    链接

    林肯[未注册用户] 2009-10-15 16:56:00

    Ivony...:

    林肯:
    @Ivony...

    Ivony...:如果测试用例已经写好,说明设计已经完备,这与敏捷的理念是背道而驰的。


    不知道您所指的"与敏捷的理念是背道而驰的"所指的是那个理念?



    以下摘自敏捷宣言:
    对我们而言,最重要的是通过尽早和不断交付有价值的软件满足客户需要。
    经常交付可以工作的软件,从几星期到几个月,时间尺度越短越好。
    简单——尽可能减少工作量的艺术至关重要。



    试想,一个完备的设计在几个星期到几个月里面都做出不来,又怎么可能交付可以工作的软件?


    我想先定义我所理解的 *可以工作的软件*:
    可以工作的 != 是指功能完备的.
    比方说,一个计算器,它哪怕只能计算加法, 它对最终用户来说都是有价值的,它是可以工作的,虽然它不是功能完备的.
    放到我们日常的开发过程中,每一个story每一个iteration都会提交这样对用户有价值的功能点,它们都可以工作, 但也许直到最后一个迭代完成,它才是功能完备的.

    然后我想定义我所理解的 *完备的设计*:
    所谓的完备的设计,我认为,它是约束在当前需要的功能的前提下的. 对于一个只有加法功能的计算器而言, 只要它能满足用户提出的所有需求,那它的设计就是完备的.
    举例说, 我的加法计算器不能算浮点数, 但是当前story里的Acceptance Criteria说明了只需要整数加法. 虽然我的加法计算器在大多数人眼里都是设计愚蠢的, 但是对这个story来说, 它便是完备的设计, 它就是可以工作的软件.

    什么时候不完备?
    当一个新的迭代到来,有一个story说需要支持浮点数加法.
    那这时,我TDD,我写到:
    assert_equal 0.2, Calc.add(0.1, .01)
    我发现跑这个测试会失败, 这时说明了我的设计不够完备,现有代码不满足要求.

  80. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-10-15 17:12:00

    林肯:
    这位老兄你真的先了解一下TDD再来吧


    紫色阴影:
    你需要恶补一下知识,有时间去看Kent beck的测试驱动开发


    我真悲剧啊 现在都流行这种讨论方式么?

  81. 老赵
    admin
    链接

    老赵 2009-10-15 17:17:00

    @winter-cn
    不过事实上,你说的TDD,或者说你对TDD的看法,的确不是我们口中已经存在的那个TDD的概念,呵呵。

  82. 麦穗
    *.*.*.*
    链接

    麦穗 2009-10-15 17:20:00

    帅哥,我能谈些我的看法不?

    1. 写产品代码
    2. 为产品代码写测试
    3. 发现测试不容易写,于是重构产品代码
    4. 重构测试
    5. ……

    可行不?如果可行,就不会出TDD了。我不知道帅哥你周围的程序员是否都有这么好的习惯,我认识的很多程序员都没有时间来写单元测试。如果老板要求,就随便补些单元测试用例。

    TDD 其中一个目的,我想就是为了避免写完代码后懒得(没有时间等N多理由)写测试用例。(先写测试用例,在写代码)。

    所以个人同意包建强帅哥的评论。您这篇文章相比别的文章,逊色些(目前只看“上”来说)。

    个人看法哈。嘿嘿,跟你学了不少东西,谢谢哈!!

  83. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-10-15 17:27:00

    Jeffrey Zhao:
    @winter-cn
    测试驱动开发,它的测试就是单元测试,不是来源于产品feature,来源于被测试者的目的。
    例如“Calculator”的Add操作,它不是产品的feature,它是产品的螺丝钉。
    测试驱动开发,就是为这个螺丝钉的目的先写测试,再写这个螺丝钉。


    我想核心的问题就是 “这个测试是不是指单元测试” 你说的确实是单元测试的例子没错,但是我从来没有见过细化到单元测试的TDD。
    我看到的资料和参与的实践中,这个测试指的都不是单元测试。虽然不是权威资料 可以看看英文wiki写的:
    In test-driven development, each new feature begins with writing a test.

  84. 老赵
    admin
    链接

    老赵 2009-10-15 17:29:00

    @麦穗
    这不是习惯,这是规范,就像TDD如果在某个公司是个行为规范一样。
    我倒不在意推广难度,我只考虑这是不是一个好的实践,投入产出比等等。
    我觉得吧,写单元测试可以节省时间,不写反而浪费。
    如果不领悟这点的话,就算强制TDD,又能写出什么样的TDD代码呢?

    // 包子是认为内容干瘪空洞,你是认为内容不正确,两者还是不一样的,呵呵。

  85. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-10-15 17:30:00

    @winter-cn

    我们的观点基本一致。

  86. 道法自然
    *.*.*.*
    链接

    道法自然 2009-10-15 17:31:00

    “‘从来没人说过,TDD适合一切,不过,绝对可以说unit test对大多数系统来说都是需要的。’ 我非常赞同.”

    可能目前我们暂时也只能先做到这吧,虽然TDD画了一张非常诱人的蓝图。

  87. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-10-15 17:32:00

    @winter-cn

    或者说我现在想修正一下观点了。

    我想我们现在过于执着于,TDD中的Test到底指的是什么测试。单元测试还是其他?我承认恐怕把讨论引入这个方向我也要负一部分责任,但我现在的观点是,TDD中的Test并不具体指某种测试方式。TDD指的是我们应该以通过测试而不是满足设计作为编写代码的目标。

  88. 老赵
    admin
    链接

    老赵 2009-10-15 17:33:00

    @winter-cn
    In test-driven development, each new feature begins with writing a test.
    这个测试就是指单元测试啊。
    TDD的示例互联网上也很多,光看“一句话”,缺乏上下文的确很容易误会的。
    所以其实,我也建议你先去系统了解一下TDD比较好,呵呵。

  89. 老赵
    admin
    链接

    老赵 2009-10-15 17:34:00

    @Ivony...
    其实TDD的想法是:测试是根据设计而写的,先让测试满足设计,再让代码满足测试,就达到了最终的目的。
    TDD中的T,的确就是指开发人员写的“单元测试”。

  90. 林肯[未注册用户]
    *.*.*.*
    链接

    林肯[未注册用户] 2009-10-15 17:44:00

    winter-cn:

    林肯:
    这位老兄你真的先了解一下TDD再来吧


    紫色阴影:
    你需要恶补一下知识,有时间去看Kent beck的测试驱动开发


    我真悲剧啊 现在都流行这种讨论方式么?



    连TDD是不是单元测试, TDD是不是开发人员来写这种最基本的问题都没清楚, 别人给你提出建议看书补习一下,已经是很客气了.

    先说明以下所有言论不真对任何测试工程师, 只是就事论事.

    老赵提出的所谓可测这个概念比较暧昧,对于一个测试工程师而言,没有可测不可测只有代价大小


    1)老赵讲的是单元测试. 实现代码写的不好,耦合度高,就会使的单元测试很难写.所以他说提高可测试性,降低代码间的耦合度.
    2)他面向的是开发人员, 与测试人员相关.
    3)议题: TDD. 测试人员更多写的是功能测试, 功能测试是没有 TDD 中 *driven* 的能力的,因为它粒度太大,即使失败了,它也只能说function break. 它并不能告诉你错误的代码发生在哪里.

    第二点 只有单元测试是依赖设计的 测试应该根据需求来确定而非设计 所以测试与设计应该是不相关的 根据测试改进设计的说法就很奇怪了


    单元测试依赖设计的话,那它就是先写代码后写测试了,那它还是TDD么?
    TDD是什么?最简单说,你根据需求写一个测试,然后运行它, 只要它不通过,就说明了现在的设计是不满足需求的,是有缺陷的.进而改进设计直到通过刚才的测试,以及所有其他的单元测试.这时设计才是满足所有已知需求的. 这就是*根据测试改进设计*.

  91. 麦穗
    *.*.*.*
    链接

    麦穗 2009-10-15 17:48:00

    @Jeffrey Zhao
    恩,这个倒是。

    其实任何规范,都是个强制到习惯的过程。最近在研究TDD,感觉很别扭,就好像本来习惯右手拿筷子,现在让用左手拿一样。

    可不可以这样理解,帅哥你的TDD就是让人右手拿筷子,只不过要严格遵循拿起筷子-》夹菜-》看看菜是啥-》在吃。
    一般人,不写测试用例的,跟加起来就吃差不多。嘿嘿。

    包帅哥认为你的内容干瘪 -》 可以理解为比你别的文章逊色
    我认为不正确 -》 可以理解为比你别的文章逊色。
    殊途同归 :)

    此为, 您的TDD的新意在哪?:)

  92. 老赵
    admin
    链接

    老赵 2009-10-15 17:52:00

    @麦穗
    我的TDD和传统的TDD的方式区别在于:
    传统的TDD用T来驱动D,先写T,再让D满足T。
    而我的TDD是,先D后T,边D边T,在T的过程中发现好困难,于是修改D的方式,让T变得简单。

  93. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-10-15 17:54:00

    林肯:

    winter-cn:

    林肯:
    这位老兄你真的先了解一下TDD再来吧


    紫色阴影:
    你需要恶补一下知识,有时间去看Kent beck的测试驱动开发


    我真悲剧啊 现在都流行这种讨论方式么?



    连TDD是不是单元测试, TDD是不是开发人员来写这种最基本的问题都没清楚, 别人给你提出建议看书补习一下,已经是很客气了.

    先说明以下所有言论不真对任何测试工程师, 只是就事论事.

    老赵提出的所谓可测这个概念比较暧昧,对于一个测试工程师而言,没有可测不可测只有代价大小


    1)老赵...




    发现一个很有意思的现象,这边每个人的观点都不完全相相异又不完全一致。

    首先我认同您说的“可以工作的软件”这一点的定义上我们应该是毫无二致的,可以看我上面的回复。其次我也认同你刚刚提出的TDD中的测试(暂且不论这个测试是不是单元测试)是不能依赖于设计的,而应该是设计依赖于测试。否则这个测试就没法去驱动了。

  94. 麦穗
    *.*.*.*
    链接

    麦穗 2009-10-15 17:57:00

    @Jeffrey Zhao

    传统TDD : 饿了,想吃番茄炒蛋,就做,想吃咸点的,就加点盐。然后感觉咸了,就加水。
    您的TDD : 先做番茄炒蛋,一边做,一边吃,觉得咸了,就加点水,觉得淡了,就加点盐。

    可以这么理解吗?

    耽误您时间了。呵呵,期待看下文。看看您怎么做番茄炒蛋的:)

  95. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-10-15 18:08:00

    Jeffrey Zhao:
    @winter-cn
    In test-driven development, each new feature begins with writing a test.
    这个测试就是指单元测试啊。
    TDD的示例互联网上也很多,光看“一句话”,缺乏上下文的确很容易误会的。
    所以其实,我也建议你先去系统了解一下TDD比较好,呵呵。


    我不是只看一句话 何况wiki也不是100%正确 因为我们这边run过TDD 我是个dev 我们run的时候确实是test先写的case 然后我们来写的code 单元测试还是按原来方式写的

    上面各位说我不了解TDD 概念问题我就不争了 你们说什么就是什么吧 就当做是我们组走的流程不标准 或者我们run的时候经过了裁减和调整

    我的主要质疑是,如果TDD特指单元测试,设计是什么时候做的?没有详细设计,单元测试用例能不能写,是怎么写的?

  96. 林肯[未注册用户]
    *.*.*.*
    链接

    林肯[未注册用户] 2009-10-15 18:11:00

    Jeffrey Zhao:
    @Ivony...
    其实TDD的想法是:测试是根据设计而写的,先让测试满足设计,再让代码满足测试,就达到了最终的目的。
    TDD中的T,的确就是指开发人员写的“单元测试”。



    我认为TDD的想法是: 测试是根据需求写的. 满足测试的代码及其设计, 才是满足需求的, 这是我们作一切工作的最终目标.

    对于Functional Test, 当然需求扮演的就是Story的AC

    对于Unit Test, 测试扮演的就你的测试对象的客户. 从使用者的角度出发,对你的测试对象提出各种需求.

    但是有一点必须明确, TDD并不能帮你产生优秀的设计, TDD不是银弹.
    它能做到的是保证正确性, 提高可测试性.

  97. thk_xing
    *.*.*.*
    链接

    thk_xing 2009-10-15 18:13:00

    老包啊,你有炒作的嫌疑啊~~
    一楼说学习了我相信他是出于一种对老赵的敬仰,是一种礼貌。
    还有想强调一点:有技术含量不代表好,没技术含量也不代表不好,更多的时候,技术含量太高的东西对一般人来说根本吸收不了,和“破铜烂铁”没区别

  98. 老赵
    admin
    链接

    老赵 2009-10-15 18:14:00

    winter-cn:
    我的主要质疑是,如果TDD特指单元测试,设计是什么时候做的?没有详细设计,单元测试用例能不能写,是怎么写的?


    这个质疑可以讨论,记得以前也看到过别人的这种质疑。

  99. 老赵
    admin
    链接

    老赵 2009-10-15 18:15:00

    @麦穗
    没明白这个番茄炒蛋的类比中哪个是D哪个是T……
    明天我打算写点别的,周一发布下,呵呵。

  100. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-10-15 18:25:00

    Ivony...:
    发现一个很有意思的现象,这边每个人的观点都不完全相同又不完全一致。

    首先我认同您说的“可以工作的软件”这一点的定义上我们应该是毫无二致的,可以看我上面的回复。其次我也认同你刚刚提出的TDD中的测试(暂且不论这个测试是不是单元测试)是不能依赖于设计的,而应该是设计依赖于测试。否则这个测试就没法去驱动了。


    我基本上同意Ivony的理解 TDD的Test不特指单元测试 但是可以包括单元测试。
    我做过的TDD第一个case一般是bootable application 之后按feature一组case一组case往上加 流程基本跟wiki说的一样。

    单元测试一般不会启动整个程序,而是调用一些类或者函数来测试,按照我的理解,TDD的case不是这样做的。

  101. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-10-15 18:39:00

    Jeffrey Zhao:
    这个质疑可以讨论,记得以前也看到过别人的这种质疑。


    老赵你不是在微软呆过? 我不相信微软有Team run TDD的时候case是dev写的 Test在旁边跟往常一样写自己的test case


    PS. to那位叫做“林肯”的很客气地老兄,
    你用再肯定的语气说,你就算再BS我的水平,如果没有提供任何论据,都不能增加你的话的可信程度。

  102. 老赵
    admin
    链接

    老赵 2009-10-15 18:43:00

    @winter-cn
    我在微软的时候还没有说起过TDD的实践,而且从现在看来,至少我的项目中单元测试做的是很不好的,呵呵。
    Test写的Case和TDD无关阿,他们写的是功能测试,不是单元测试。
    TDD是指开发人员利用单元测试辅助开发的一种做法,T单指单元测试。

  103. 老赵
    admin
    链接

    老赵 2009-10-15 18:46:00

    @winter-cn
    当然,你说的也可以叫做是TDD,不过对于我见过的对于TDD的描述和讨论,都是指Unit Test,而不是其他任何类型的Test。
    我觉得,我们还是指讨论Unit Test而不要牵扯其他种类的Test吧,因为我没见到有人说应该用“功能测试”来驱动开发。
    说真的,我是今天才知道有人会认为TDD的T还包含非单元测试的测试,呵呵。

  104. 老赵
    admin
    链接

    老赵 2009-10-15 18:48:00

    话说,TDD是Kent Beck提出的吧,在他的书,也是第一本描述TDD方式的书里已经写的很明白了,说T就是指单元测试,而不是泛指“用任何一种测试”来“驱动开发”。

  105. 林肯[未注册用户]
    *.*.*.*
    链接

    林肯[未注册用户] 2009-10-15 19:00:00

    winter-cn:

    Jeffrey Zhao:
    这个质疑可以讨论,记得以前也看到过别人的这种质疑。


    老赵你不是在微软呆过? 我不相信微软有Team run TDD的时候case是dev写的 Test在旁边跟往常一样写自己的test case


    PS. to那位叫做“林肯”的很客气地老兄,
    你用再肯定的语气说,你就算再BS我的水平,如果没有提供任何论据,都不能增加你的话的可信程度。


    首先,我没有鄙视你的水平,如果让你有这种感觉,我抱歉.
    其次,在我的回复里,对你的两点问题,我提出了解答.请阅读.

  106. 林肯[未注册用户]
    *.*.*.*
    链接

    林肯[未注册用户] 2009-10-15 19:05:00

    Jeffrey Zhao:
    @winter-cn
    当然,你说的也可以叫做是TDD,不过对于我见过的对于TDD的描述和讨论,都是指Unit Test,而不是其他任何类型的Test。
    我觉得,我们还是指讨论Unit Test而不要牵扯其他种类的Test吧,因为我没见到有人说应该用“功能测试”来驱动开发。
    说真的,我是今天才知道有人会认为TDD的T还包含非单元测试的测试,呵呵。


    同意.
    我觉得大家有个误区, 把Test First和Test Driven Design/Development 混为一谈.

    前面我已经解释了为什么我觉得非单元测试为什么难以*驱动* Design/Development.

    当然,尽管非单元测试难以驱动开发, 但是Test First仍然能给我们带来大量的好处.

  107. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-10-15 19:17:00

    特地看了一遍TDD by example
    看来理清基本概念还是非常重要的:

    Can you drive development with application-level tests?
    The problem with driving development with small scale tests (I call them “unit
    tests”, but they don’t match the accepted definition of unit tests very well) is that
    you run the risk of implementing what you think a user wants, but having it turn out
    to be not what they wanted at all.

    如果我们用通常的UT的概念去理解TDD里的UT 显然是不恰当的

    从这个描述看 这里被称作unit tests的small scale tests是针对功能的测试无疑。

    所以老赵我觉得你有必要重新审视下你对于TDD的观点了

  108. 林肯[未注册用户]
    *.*.*.*
    链接

    林肯[未注册用户] 2009-10-15 19:44:00

    Jeffrey Zhao:

    winter-cn:
    我的主要质疑是,如果TDD特指单元测试,设计是什么时候做的?没有详细设计,单元测试用例能不能写,是怎么写的?


    这个质疑可以讨论,记得以前也看到过别人的这种质疑。


    先回答后面这个问题.
    没有详细设计,单元测试可以写.方法是把自己当作测试对象的客户,对它提出各种需求,用测试的形式表现出来.比如,我要一个加法计算器:
    a = random.to_i
    b = random.to_i
    assert_equal a + b, Calc.add(a, b)
    通过这个测试,就说明至少Calc类可以计算整数加法了.当然一开始这个测试是失败的,失败的测试就是驱动你添加/改写现有代码的动力.

    前面那个问题: 设计是什么时候做的?
    我自认为才疏学浅,远没有能够轻松的refactoring to patterns. 所以在重要问题上要说一点预先设计没有是不可能的,通常都会集体讨论深思熟虑后做一些基本的设计,比如系统的基本架构上. 但是我们会让它尽量simple and simple enough. 尽量的足够的简单,又恰好满足需求, 同时还易于修改.

    对于class甚至是story级的设计,基本全部是由测试驱动出来的. 当然就像我说的, TDD出来的设计未必就是优秀的设计, 你需要在代码的演进过程中不断的去重构它, 把它演进成足够简单又符合需求的优秀设计.

    这个问题争议比较多, 以上只是我个人的体会,欢迎大家斧正.

  109. 老赵
    admin
    链接

    老赵 2009-10-15 19:59:00

    @winter-cn
    没看出来这是在说TDD的测试是针对产品功能的测试。
    我觉得,既然《TDD by example》,里面肯定有example吧,难道它们不是单元测试吗?
    我敢肯定,TDD by Example这本书,其中的测试都是开发人员写的,不会涉及到测试人员,呵呵。

  110. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-10-15 20:00:00

    @林肯
    不要为了证明TDD是单元测试 最后把TDD搞成没有设计全凭感觉走 最后等重构
    我上面已经说了 TDD所谓的UT跟一般意义上的UT不是同一个概念

  111. 老赵
    admin
    链接

    老赵 2009-10-15 20:05:00

    @winter-cn
    你觉得TDD的UT和一般意义上的UT,它们分别是什么,区别在哪里呢?
    我看过的任何TDD资料,上面都是一般意义上的UT啊。

  112. 林肯[未注册用户]
    *.*.*.*
    链接

    林肯[未注册用户] 2009-10-15 20:08:00

    winter-cn:
    @林肯
    不要为了证明TDD是单元测试 最后把TDD搞成没有设计全凭感觉走 最后等重构
    我上面已经说了 TDD所谓的UT跟一般意义上的UT不是同一个概念


    我已经说的很清楚了, 设计跟着测试走, 测试跟着需求走.
    失败的测试就是你实现或者改变设计的动力, 通过的测试就是你的设计正确性的证明.

  113. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-10-15 20:26:00

    @林肯
    除了API项目 需求不会告诉你你需要哪些类 类里面要有什么方法
    你不知道需要哪些类 又不知道类里有什么方法 你怎么写单元测试?

    你思路太混乱了 自己好好整理整理吧

  114. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-10-15 20:44:00

    其实我现在觉得大家的观点在趋同了,或者都是该死的概念捣的鬼。

    其实我原来的问题和writer-cn的一致的,如果TDD中的T是一种小粒度的Unit Test,那么势必我们要设计出所有的Unit,而在我的概念中,如果所有的Unit都设计完了,剩下的工作就是编码,而没有设计啥事儿了。这也就是我说的完备的设计。而这个就我看来是与敏捷有些背道相驰的。

    所以后面的讨论中,我们可以看出来其实这是因为大家对Unit Test的理解不一致所导致的,我比较赞成林肯的观点。测试应该是跟着需求走的,只有这样,应该说才符合敏捷的思想。因为我们的目标就是实现“现有的”需求,换言之是“能工作的”软件而不是“设计完备的”软件。

  115. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-10-15 20:58:00

    Jeffrey Zhao:
    @winter-cn
    你觉得TDD的UT和一般意义上的UT,它们分别是什么,区别在哪里呢?
    我看过的任何TDD资料,上面都是一般意义上的UT啊。



    TDD是开发方法 这里的测试 怎么可能局限于UT呢
    他这里所说的small scale tests,可能是一般意义上的UT 也可能是简单的小的功能测试
    如果是举例子讲解TDD 不可能有人喜欢拿出整套UI测试框架来做例子吧? 但是这不能说明必须UT才能TDD

  116. 猪里爷[未注册用户]
    *.*.*.*
    链接

    猪里爷[未注册用户] 2009-10-15 21:32:00

    #113楼 winter-cn 2009-10-15 20:26
    @林肯
    除了API项目 需求不会告诉你你需要哪些类 类里面要有什么方法
    你不知道需要哪些类 又不知道类里有什么方法 你怎么写单元测试?

    你思路太混乱了 自己好好整理整理吧


    写测试用例 = 设计接口(服务) -> 测试用例驱动开发啊

    我是这么理解的
    TDD是针对开发人员的吧,不然最后的D是谁做的?开发人员参与的测试也就单元测试,那么为什么TDD不是针对的单元测试呢?

  117. 猪里爷[未注册用户]
    *.*.*.*
    链接

    猪里爷[未注册用户] 2009-10-15 21:57:00

    写UT时还无代码实现,但要包含对外的接口,这时就有设计,这里就将开发融合到了具体场景里去了,而在传统的开发模式中,服务接口前期并未直接参与到具体场景,都是完全实现后的再调用,凭空设计的风险就要大。

  118. 王德水
    *.*.*.*
    链接

    王德水 2009-10-15 21:58:00

    @Jeffrey Zhao
    我们也搞了半年的TDD,最终回归单元测试,心得如下:
    1. TDD很难驱动设计,TDD可以改良设计
    2. TDD主要是站在用户(最终用户或者框架设计者面临的开发者)的角度,防止软件偏离客户,也就是一开始就要知道“How to demo”,减少代码提交后返工的次数。
    3. TDD 其实不是用来测试设计的,主要是测试功能的,然后,必须要代码具有较高的测试性。
    4. TDD 尤其对敏捷类的,需求不断变化的项目只会减少总时间
    ....

    我们没有搞好TDD的原因,是我们的设计能力有问题,很多代码耦合较多,我们曾经想用TypeMock这个武器,但是又觉得偏离了TDD。

    但是,我就奇怪以老赵的设计能力为何还会对TDD表示怀疑?设计易测试的代码肯定不是问题。

  119. 猪里爷[未注册用户]
    *.*.*.*
    链接

    猪里爷[未注册用户] 2009-10-15 22:13:00

    TDD是针对的单元测试,应该无错。
    winter的wiki地址所指的feture其实相等于Case,至于单元的粒度大小,可以实践来取经。
    TDD主要是可平稳的迭代开发,坡度较平稳,方便回归测试等。
    至于楼上的一说,就算你不采用TDD,程序设计上还是需要经历磨练,通过结合场景写UT,这里就已经在考验设计了。

    说的不好,请拍砖。

  120. 老赵
    admin
    链接

    老赵 2009-10-15 22:34:00

    winter-cn:
    TDD是开发方法 这里的测试 怎么可能局限于UT呢
    他这里所说的small scale tests,可能是一般意义上的UT 也可能是简单的小的功能测试
    如果是举例子讲解TDD 不可能有人喜欢拿出整套UI测试框架来做例子吧? 但是这不能说明必须UT才能TDD


    对于他说的话,其实有个“解读”的成分在里面,我真没读出来他指出其他测试了。
    至于示例……我也实在没有看到过一个不用单元测试而是其他测试的TDD例子,看了很多了例子了。
    如果真的TDD不仅仅是UT的话,我也总该听说过吧……
    我感觉,你的看法不是三句两句话能说清的,可能现在讨论半天也没有结果,最好的做法是找出实例,亲手写几个,然后写它八千字的,呵呵。

  121. 老赵
    admin
    链接

    老赵 2009-10-15 22:37:00

    王德水:
    但是,我就奇怪以老赵的设计能力为何还会对TDD表示怀疑?设计易测试的代码肯定不是问题。


    我没有怀疑,我不是说了我相信TDD的功效吗?只是我一直做不到传统意义上严格的TDD而已。
    我的TDD,不也是为了可测试性而不懈努力吗?

  122. 林肯[未注册用户]
    *.*.*.*
    链接

    林肯[未注册用户] 2009-10-15 22:49:00

    winter-cn:
    @林肯
    除了API项目 需求不会告诉你你需要哪些类 类里面要有什么方法
    你不知道需要哪些类 又不知道类里有什么方法 你怎么写单元测试?

    你思路太混乱了 自己好好整理整理吧



    如果什么都告诉我了,或者我什么都知道了,我还设计啥啊.
    Design emerge from code written.
    正是通过不断的 *新的测试* -> *测试失败* -> *改写代码* -> *测试通过* -> *新的测试* .....这样的循环, 你所设计的类才会逐渐有血有肉起来,而且这样驱动出来的类,虽然不一定是最好的设计,但一定很lean. 它所有的外在行为都是由确切的使用者提出,而且符合使用者的预期.

  123. 林肯[未注册用户]
    *.*.*.*
    链接

    林肯[未注册用户] 2009-10-15 23:03:00

    @猪里爷

    猪里爷:写UT时还无代码实现,但要包含对外的接口,这时就有设计,这里就将开发融合到了具体场景里去了,而在传统的开发模式中,服务接口前期并未直接参与到具体场景,都是完全实现后的再调用,凭空设计的风险就要大。


    赞同.
    传统的设计,可以说是based on assumptions and experiences.
    敏捷的设计,可以说是based on fact and usage.

    传统的设计,可以说是refuse change.
    敏捷的设计,可以说是embrace change.

    在作一些需求非常明确,一定不会变化的项目的时候, 传统的设计方式可能会比敏捷的方式更有效率.

    But remember, the only thing that is constant is that : THINGS CHANGE.

  124. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-10-15 23:04:00

    Jeffrey Zhao:

    winter-cn:
    TDD是开发方法 这里的测试 怎么可能局限于UT呢
    他这里所说的small scale tests,可能是一般意义上的UT 也可能是简单的小的功能测试
    如果是举例子讲解TDD 不可能有人喜欢拿出整套UI测试框架来做例子吧? 但是这不能说明必须UT才能TDD


    对于他说的话,其实有个“解读”的成分在里面,我真没读出来他指出其他测试了。
    至于示例……我也实在没有看到过一个不用单元测试而是其他测试的TDD例子,看了很多了例子了。
    如果真的TDD不仅仅是UT的话,我也总该听说过吧……
    我感觉,你的看法不是三句两句话能说清的,可能现在讨论半天也没有结果,最好的做法是找出实例,亲手写几个,然后写它八千字的,呵呵。


    果然是100个Team run的敏捷有100种样子

    作者把应用程序级测试用例驱动称为ATDD
    关于ATDD 作者自己的说法是:
    TDD as described in this book is a technique that is entirely under your control. You can pick it up and start using it today if you so choose.

    大概算是未置可否吧

  125. 陈根发的BLOG
    *.*.*.*
    链接

    陈根发的BLOG 2009-10-15 23:08:00

    一帮自以为是的家伙,blog只是一个交流学习的场所。跟文章水不水有什么关系,

  126. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-10-15 23:19:00

    @林肯
    你说的全都没错 但是你没有回答我的问题 也没有证明TDD中的测试必须是单元测试是合理的

    我一直在问新的测试从何而来,你始终无法回答这个问题:

    根据需求 能否直接写出单元测试用例?

    我的看法是不能 单元测试必须在类的基本结构确定之后才能写出来,否则就不能称为单元测试了。

  127. 猪里爷[未注册用户]
    *.*.*.*
    链接

    猪里爷[未注册用户] 2009-10-15 23:38:00

    to winter
    就是根据需求写相应的用例,这时是没有相应实现,就需要根据需求来设计类对外开放的接口。如何设计呢,不是光凭需求来凭空想象,而是以代码的形式模拟场景。这时就选择了测试用例来模拟案例。如何测试,修改等迭代最终迭代出符合实际的产出代码的。

  128. 林肯[未注册用户]
    *.*.*.*
    链接

    林肯[未注册用户] 2009-10-15 23:56:00

    winter-cn:
    @林肯
    你说的全都没错 但是你没有回答我的问题 也没有证明TDD中的测试必须是单元测试是合理的

    我一直在问新的测试从何而来,你始终无法回答这个问题:

    根据需求 能否直接写出单元测试用例?

    我的看法是不能 单元测试必须在类的基本结构确定之后才能写出来,否则就不能称为单元测试了。


    新的测试从何而来?
    回答:从新的需求而来.
    举例:
    ==============================
    Iteration 1: 需要一个计算器,只需支持整数加法
    添加测试:assert_equal 2, Calc.add(1, 1)
    测试失败
    创建Calc类,添加add方法,(通过IDE和重构工具的支持, 比如resharper和intelij, 只不过按几下alt+enter)
    实现.
    测试通过.
    提交代码,客户很happy,开心的玩去了.
    ==============================
    Iteration 2: 需要之前的计算器,能够支持整数减法
    添加测试: assert_equal 1, Calc.sub(3, 2)
    测试1通过,测试2失败
    添加sub方法
    实现
    测试1通过,测试2通过
    提交代码,客户很happy,开心的把项目结束了.
    ==============================
    在我看来,当类的基本结构已经确定下来的时候, 像我这样的程序员很容易产生先入为主的偏见,很容易就将单元测试写成实现代码的简单重复.
    另外一个重要的问题是, 在迭代式开发的背景下,我永远也不知道我的类什么时候算是基本稳定下来, 甚至有些时候它会在下一个迭代到来的时候被完全推翻.
    当一个类真的确定下来了,那一定是项目结束了.

  129. ERic Poon
    *.*.*.*
    链接

    ERic Poon 2009-10-16 00:27:00

    似乎这个TTD还是挺乎合现有的需求.而且比传统的TDD节省了时间和代码量.

  130. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-10-16 00:37:00

    我想两位不必再拘泥于单元测试这个概念。
    现在至少我们知道的是,并没有人明确的说TDD中的T指的就是Unit Test,这个要等老赵来举证,呵呵,或许可以继续辩论。其次,就算是Unit Test,也没有一个明确的定义。或许在这里说的Unit Test不是传统理解中的Unit Test呢?

    我大体上是同意林肯的观点的。

    测试从何而来。
    这一点我是赞同测试必须从需求来的。这才是敏捷的风格,敏捷开发与传统开发的理念上的根本不同就是敏捷的目标是完成需求而不是合同。既然用某个东西来驱动开发,则这个东西必然要从目标而来。目标是什么?传统的瀑布开发,每一步的目标都是承上启下的。测试阶段的目标是修正编码的错误。所以测试的目标很明确,是验证。敏捷则不是。

    敏捷有一个很重要的原则:“可以工作的软件是进度的主要度量标准”。所有的敏捷实践中,无论是怎样的进度控制,其评价标准一定是“可以工作的软件”,而不是某个阶段性成果。换言之极端点说,就算是所有的单元测试用例都已经写好在我看来等于什么都没有做。因为并没有可以工作的软件。没有可以工作的软件,就无法评价你的工作成果(所有的测试用例)是不是符合最终目标的。


    很多人认为,TDD的目的在于保证代码质量。我想问题应该不在于此,如果只是为了保证代码质量,Test在前在后是没有关系的。如果要将TDD与敏捷结合起来看,我觉得TDD是为了确保设计的简单这会比较靠谱。(或者,我大胆的猜测下,存在两种TDD,一种是敏捷的,一种是非敏捷的。毕竟TDD出现在敏捷理念流行之前)

    或者说在敏捷的范畴里,TDD是一种“简单——尽可能减少工作量的艺术”。

    为什么?
    我们来看看如果不是TDD的话,我们会怎样来开发。
    显然我们会先将需求抽象建模,做出需求的抽象模型,然后实现这个抽象模型,再加上血肉交付产品。这样做的理论依据也是很充分的,需求随时可能变化,但抽象的东西不会变化。
    问题是这个抽象是需要时间的,XP的实践是什么?客户就站在身边,试想客户有时间等着你去做对他而言很无聊的抽象建模么?
    甚至于,客户自己都不知道自己想要什么,或许他会给你一个方向,我要一个博客,结果在做的过程中你发现他要的其实是一个SNS或是别的什么东西。这样,你的抽象建模就变得毫无意义。

    如何在最短的时间内完成可以工作的软件呢?唯一的办法就是keep simple!我们的目标是什么?可以工作的软件。完成这个目标的评判标准是什么?那不就是测试。
    或者说我们不应该将问题分解成,完成客户的需求 = 首先根据需求作出详细的设计,根据详细的设计作出详细的单元测试,如果程序能够完成单元测试,则我们完成了客户的需求。这个过程太冗长了。
    而应该是我们完成可以验证满足需求的东西(测试)。然后让这个东西say OK。

  131. ProudSnow
    *.*.*.*
    链接

    ProudSnow 2009-10-16 00:48:00

    认真阅读了以上各位兄弟的发言,受益非浅!我也想谈一谈我的使用感受,权当抛砖引玉吧。
    我觉得TDD这个理念不是孤立的,它应是融合了其它软件设计或开发理念再加上适用的场景发展而来。
    我根据新系统需求(非客户需求,客户需求经UML分析转化成系统需求)开始写空(针对接口或Mock)测试代码的时候,我思考的是我的被测试主体应该具有什么样的行为?每个行为的输入是什么?输出又应该怎么样?这些行为或输入输出是否合理?因为这个时候被测试主体是空的,它正需要外在的工具来规范它的行为与输入输出(也许此被测试主体是其它主体的辅助体,
    在设计前它没有明显的输入输出)。当被测试主体引用其它的资源,而我又不想被此资源干扰,只想要针对我的被测试主体来进行思考时,我会使用Mock。在这一阶段(写空测试代码时)其实就是在进行最高层(即抽象)设计,被测试主体将来是一个对象或一组对象此时我不考虑,反正此被测试主体一定要表现出这些行为来,这样就可以针对接口(interface)设计与测试了。在此阶段如果发现被测试主体的接口违反一些软件设计的基本原则或违背一些显而易见的设计模式应马上重构被测试主体代码和测试代码(这时重构成本较低)。如果是新增加的系统需求也一样,只是在必要的时候进行模式重构。如果只做设计,这一阶段完成后(也可能是做了一些实现后)就可以交给做实现的同事再继续往后做了。
    接下来就是写实现代码,此时我思考的是我应该如何根据测试规定出的接口合理地组织对象了,那么显然在这个阶段设计模式又会拍上大用场。我会考虑应该用几个对象?对象间的关系如何?这几个对象间的关系是否紧密?有没有现成的模式可套?如果此时我发现有些对象间的关系较松散就会重构实现部分代码甚至测试部分代码,重构原则上以测试规定出的接口为准,即先重构实现,在确有重构接口的必要时再重构测试规定出的接口。如果有多个对象相互协作,那么根据需要针对个别晦涩的对象循环进入写测试阶段。
    当实现通过了测试时此过程完毕否则循环进入写测试阶段。
    以上是我使用TDD的体会,欢迎共同探讨。我想TDD也只是一种方法,写测试代码就是在做设计,如果不设计怎么能写出高质量的测试代码呢?并且TDD的概念架构在Unit Test概念之上,也即测试代码本身也应该是可重入的。所谓可重入是指在测试同一代码时前一次的测试不影响后一次的测试,即要做好测试前的初始化工作和测试后的收尾工作。TDD侧重于先设计后验证,这样能更早也更快速的发现并改正问题。但软件设计的其它方法还是要用上的,因为它们本身也不冲突。我想这是不是就像武侠小说中常提到的那柄心中剑呢?

  132. farstar[未注册用户]
    *.*.*.*
    链接

    farstar[未注册用户] 2009-10-16 09:39:00

    Robert C.Martin在他的书《敏捷软件开发》中描述过一次编程实践(保龄球比赛计分程序),涉及了TDD和结对编程,一开始也是先做了一个设计草图,然后开始TDD,也是从单元测试开始,最后演变的结果是单元测试完整的描述了需求,而最后的代码与最初的设计草图大相径庭。他的话给人一个初步印象是开始的设计不必要的,而是应该从测试开始。
    不过感觉上虽然一开始的设计和最后的代码差异挺大,可如果没有这个初步设计,似乎也不好开始设计测试。

  133. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-10-16 10:09:00

    其实这并非一个简单的概念问题 而是究竟TDD的测试应该怎么写?

    林肯:
    新的测试从何而来?
    回答:从新的需求而来.
    举例:
    ==============================
    Iteration 1: 需要一个计算器,只需支持整数加法
    添加测试:assert_equal 2, Calc.add(1, ...


    如果你的计算器不是只有一个类 你如何决定给哪个类添加一个add方法?

    你在写test之前 已经决定了要由Calc完成你的需求 这显然已经不是test drive了

  134. 林肯[未注册用户]
    *.*.*.*
    链接

    林肯[未注册用户] 2009-10-16 10:11:00

    130楼Ivony同学和131楼ProudSnow同学都说的很好.

    130楼里,Ivony很清楚的说明了TDD背后的价值观,即通过TDD,我们追求的目标是什么.
    131楼里,ProudSnow同学说了TDD的方法论,我们具体怎么实践它.

    即我们追求的是简单,切合实际情况的设计,以及相应产生的简单精益的实现.

    我并不想争论TDD的T是什么T, 但是大家必须把握一个原则, 这里的T必须能够DD.
    我个人觉得Function Test及以上scale的T难以DD,原因是粒度太大,一个失败的Function Test带给dev的信息量及指导意义,和直接阅读Story并没有大的区别.

    虽然Function Test难以DD, 但是它不妨碍我们对它进行Test First.
    就像winter-cn同学的实践, 对Function Test进行Test First一样会给整个项目的开发流程带来巨大的好处.

  135. 林肯[未注册用户]
    *.*.*.*
    链接

    林肯[未注册用户] 2009-10-16 10:31:00

    winter-cn:
    其实这并非一个简单的概念问题 而是究竟TDD的测试应该怎么写?

    林肯:
    新的测试从何而来?
    回答:从新的需求而来.
    举例:
    ==============================
    Iteration 1: 需要一个计算器,只需支持整数加法
    添加测试:assert_equal 2, Calc.add(1, ...


    如果你的计算器不是只有一个类 你如何决定给哪个类添加一个add方法?

    你在写test之前 已经决定了要由Calc完成你的需求 这显然已经不是test drive了


    这里我是站在Calc的使用者角度出发,我认为Calc应该为我提供add的接口,至于Calc类怎么去实现add, 这不是Calc的使用者需要担心的问题.我并没有决定具体的实现在哪里.我只要求了Calc给我提供的add接口符合我的要求.

    假如如你所说,Calc使用了其他的类,比如Math, 那我就要站在Calc的角度,向Math提出需求.假如Math已经有了符合要求的add方法,那我就直接使用.(假设所有的代码都是TDD出来的,所以我可以放心大胆的使用Math.add方法)
    假如Math没有add方法,那我就要再次TDD了:
    assert_equal 2, Math.add(1, 1)
    至于Math怎么去实现add方法,不是我Calc需要关心的.

    回到最初Calc的使用者, 只有这样, 使用者才不会去关心任何add的实现细节, 你将来用SuperMath实现,用ASM实现, 我不care. 这样也降低了各级代码各个类之间的耦合. 这就是TDD出来的简单,干净的设计.

  136. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-10-16 10:34:00

    @林肯

    我想现在TDD的全景已经开始慢慢展现。

    TDD的核心是Test这是毋庸置疑的。Test必须从需求而来,这一点上至少我是确信并认为只有这样才是敏捷的,才可能去驱动。

    那么直接从需求来的Test能不能驱动我们的开发呢?
    我想对于很多小型或是功能性的项目这是可以的。
    就比如说计算器、编译器、解释器或是模版引擎什么的,这些东西很明显的我们可以在不做任何设计之前,仅仅通过其功能的定义和规约就能做出Test。

    但是有时候需求会是一种抽象的东西。这个时候我想我们应该将需求具象化。例如客户说我要做一个论坛。我们应该将需求具象为一个由可以发表帖子和回复的板块组成的网站。当需求被具象后,自然也能做出Test。

    如果直接从需求出来的Test根本就没有办法去驱动我们的开发呢?或者说由于需求过于表面和繁杂(客户可能并不是一开始就提出一个简单的需求,而是一个看起来完备实际上很乱的需求)。
    或者说我会认为这不是TDD所适用的场景(尽管其实我现在遇到的都是这样的)。
    但如果真的是TDD的话,我想可以先通过需求做出全景的Test,以满足这个Test作为目标来做设计,设计出细化的功能需求,再将这些功能需求做新的Test,然后以满足这些Test为目标来进行编码。

    或者说是全景的Test驱动细化的Test,任何开发都是以满足Test为目标。

  137. 林肯[未注册用户]
    *.*.*.*
    链接

    林肯[未注册用户] 2009-10-16 11:20:00

    Ivony...:
    @林肯

    我想现在TDD的全景已经开始慢慢展现。

    TDD的核心是Test这是毋庸置疑的。Test必须从需求而来,这一点上至少我是确信并认为只有这样才是敏捷的,才可能去驱动。

    那么直接从需求来的Test能不能驱动我们的开发呢?
    我想对于很多小型或是功能性的项目这是可以的。
    就比如说计算器、编译器、解释器或是模版引擎什么的,这些东西很明显的我们可以在不做任何设计之前,仅仅通过其功能的定义和规约就能做出Test。

    但是有时候需求会是一种抽象的东西。这个时候我想我们应该将需求具象化。例如客户说我要做一个论坛。我们应该将需求具象为一个由可以发表帖子和回复的板块组成的网站。当需求...


    你说的很对.
    正因为是抽象的东西很难具象化,越business的东西,越难被dev准确的理解.

    所以我们才需要Business Analyst这样的角色去理解Business,然后写成一个个小的易于理解和实现的story, 这样也就容易快速交付, 至少快速得到feedback.
    从项目的流程方面,我们需要迭代式开发, 逐渐将抽象的东西具象化,给它不断的添加血肉.

  138. xiao_p
    *.*.*.*
    链接

    xiao_p 2009-10-16 11:20:00

    回头再看 winter-cn的“用比较细致的Test case代替概括性的Use case来描述需求”

    和Ivony在136楼的回复中的思想是多么的相似。

    我实在不明白为什么那么多人会要求winter-cn去弄本书看,替他打抱不平下。

  139. xiao_p
    *.*.*.*
    链接

    xiao_p 2009-10-16 11:21:00

    老赵这个帖子充分的达到了 让大家讨论的 目的,评论里面的精华很多,让我受益匪浅,我正在摘抄一些经典的评论,如果有机会准备发表出来。

  140. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-10-16 12:44:00

    xiao_p:
    回头再看 winter-cn的“用比较细致的Test case代替概括性的Use case来描述需求”

    和Ivony在136楼的回复中的思想是多么的相似。

    我实在不明白为什么那么多人会要求winter-cn去弄本书看,替他打抱不平下。




    这个我早就说过了:


    我觉得很多时候会陷入一个怪圈,就是每个人都强调自己的理解是最符合某个东西的定义的。但实际上争论这个是一件更无聊的事情。无论这个定义是什么级别的大师所给出的,我们所要讨论的问题应该是怎样做最好,而不是怎样做是符合某个定义的。

    包括老赵所提出的的SOLID原则。。。。


    可以说实际上writer-cn的观点和林肯的观点以及我的都已经开始趋同了,之间的分歧恐怕只是名词的问题。

    所以我个人很反感乱发明名词,例如各种模式。

  141. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-10-16 13:31:00

    @林肯

    我们是不是可以将TDD模型简化为:


    客户提出需求 -> 为需求准备测试方案 -> 试图通过测试 -> 在试图通过测试的过程中,提出新的更细化的需求 -> 准备新的细化的测试方案 -> 试图通过这些测试

    或者说用代码表示:

    /// <summary>
    /// 模块
    /// </summary>
    public class Module
    {
    
      public static Module CreateModule( Model model, Module[] modules )
      {
        throw new NotImplementedException();
      }
    }
    public class Software : Module { }
    
    public class Requirement { }
    public class ModuleRequirement : Requirement { }
    
    public class Target { }
    
    
    public class Model { }
    
    /// <summary>
    /// 测试方案
    /// </summary>
    public class TestSolution
    {
      /// <summary>
      /// 目标
      /// </summary>
      public Target Target { get; private set; }
    
      /// <summary>
      /// 测试指定的模块是否能够通过测试。
      /// </summary>
      /// <param name="module"></param>
      /// <returns></returns>
      public bool Test( Module module )
      {
        throw new NotImplementedException();
      }
    
    
      /// <summary>
      /// 上一次测试的反馈
      /// </summary>
      public TestFeedback Feedback { get; private set; }
    
    }
    
    public class TestFeedback { }
    
    
    public static class TDD
    {
    
      /// <summary>
      /// 实现需求
      /// </summary>
      /// <param name="requirement">软件的需求</param>
      /// <returns>实现了需求的软件(原型)</returns>
      public static Software Actualization( Requirement requirement )
      {
    
        return (Software) Actualization( (ModuleRequirement) requirement );
    
      }
    
    
      /// <summary>
      /// 实现需求
      /// </summary>
      /// <param name="requirement">软件的需求</param>
      /// <returns>实现了需求的软件(原型)</returns>
      public static Module Actualization( ModuleRequirement requirement )
      {
    
        var testSolution = CreateTestSolution( requirement );
    
        return Develop( testSolution );
    
      }
    
    
      /// <summary>
      /// 根据需求创建测试方案
      /// </summary>
      /// <param name="requirement">需求</param>
      /// <returns>测试方案</returns>
      private static TestSolution CreateTestSolution( Requirement requirement )
      {
        throw new NotImplementedException();
      }
    
    
      /// <summary>
      /// 开发达成指定目标的的软件(模块)
      /// </summary>
      /// <param name="testSolution">测试方案</param>
      /// <returns>达成目标且通过测试的软件(模块)</returns>
      private static Module Develop( TestSolution testSolution )
      {
    
        Module module;
    
        do
        {
          Model model;
          ModuleRequirement[] requirements;
          Analyze( testSolution.Target, testSolution.Feedback, out model, out requirements );
    
          if ( requirements == null )//需求没有必要继续细化
          {
            module = Coding( testSolution.Target, testSolution.Feedback );//直接编码
          }
          else//否则分别实现
          {
            module = Module.CreateModule( model, requirements.Select( requirement => Actualization( requirement ) ).ToArray() );
          }
        }
        while ( testSolution.Test( module ) );
    
        return module;
    
      }
    
      /// <summary>
      /// 编码以实现指定目标
      /// </summary>
      /// <param name="target">编码目标</param>
      /// <param name="feedback">上一次测试的反馈</param>
      /// <returns>实现目标的模块</returns>
      private static Module Coding( Target target, TestFeedback feedback )
      {
        throw new NotImplementedException();
      }
    
    
      /// <summary>
      /// 分析目标
      /// </summary>
      /// <param name="target">需要实现的目标</param>
      /// <param name="feedback">上一次测试的反馈</param>
      /// <param name="model">实现目标的模型结构</param>
      /// <param name="requirements">实现目标的模块需求</param>
      /// <returns></returns>
      private static ModuleRequirement[] Analyze( Target target, TestFeedback feedback, out Model model, out ModuleRequirement[] requirements )
      {
        throw new NotImplementedException();
      }
    
    
    }
    

  142. Nick Wang (懒人王)
    *.*.*.*
    链接

    Nick Wang (懒人王) 2009-10-16 13:42:00

    没看完所有的评论,只是谈谈自己的一些看法:
    TDD是单元测试不,最开始是的,因为这里的Test是开发人员写得,所以是Unit Test。但是后来慢慢发展到不限于UnitTest,最近比较火的就是BDD,可以看做是Acceptance Test。但是一般提到TDD的时候,T还是UnitTest。

    是不是所有的类、方法都要写Test,都要由Test Drive出来?不是。
    一个很常见的问题就是,为每个方法都写Test,getter、setter、constructor等等。最后的结果就是test虽然多,但是都很stupid,test出来的效果不好,对design也没有帮助。

    我觉得何时写何时不写Test取决于你对代码的信心,如果你对设计与实现的信心很足,那么完全不必要写(老赵的信心就很足啊)。这一般是因为设计比较简单,有类似的经验,或者本身水平比较高。而反过来对于没什么经验的东西,写测试是一个非常好的起点,来驱动你的设计与代码。但是信心这个东西是很主观的,你对自己信心的判断也可能是不对的,因此需要调整。如果你写了很多测试,但是测试都很简单,而且很少出错,那么就可以考虑少些这方面的测试。如果某些部分总是需要调试,实现总是不对,那么就需要考虑多写测试。

    我见到有些同学说TDD出来的大多数测试都很stupid,这跟你实现的系统特点也有关系。对于Web的一些应用,核心其实就是CRUD加一点点验证和其他逻辑,那么这里的测试点其实不是CRUD本身,而是验证逻辑、其他逻辑、以及持久化等,但是像持久化、log等有逻辑的部分,实际上已经由第三方的库或者框架解决了,因此你的测试就会变得很stupid。而且由于这种应用的架构和设计本身已经很成熟了,使用TDD来驱动设计意义并不大,所以TDD的效果也并不好。

    学习和使用TDD最有效果的方式,不是通过简单的demo,是通过解决那些逻辑比较复杂,而你又没解决过的问题。谦虚的认为自己没有能力做出完美的预先设计,通过TDD一点一点的解决问题,不断加深对问题的理解,慢慢改进设计,最终解决问题。

  143. Nick Wang (懒人王)
    *.*.*.*
    链接

    Nick Wang (懒人王) 2009-10-16 13:47:00

    TDD能解决的问题:
    1. 得到跟多的测试,验证对需求的实现
    2. 使代码具有可测性,易于模块化,从而改善设计
    3. 重构和修改代码的安全网,保证新加入的代码,不会破坏已有的功能
    4. 文档,测试是对代码使用方式和使用规范的文档
    5. 增强信心
    …………

    如果你想解决这其中的几个问题,是否可以通过使用TDD来解决?
    不一定,或许其他方法更好,但是可以先试试TDD。

  144. Nick Wang (懒人王)
    *.*.*.*
    链接

    Nick Wang (懒人王) 2009-10-16 13:58:00

    老赵:
    虽然TDD是从测试开始,但是写测试的时候实际上已经就是在设计API了。所以一开始就有设计的“草图”不仅是没有问题的,而且是很应该的。

    “我还是先写代码,再写测试,用测试来检查代码的实现和“期望”是否相符”
    测试就是用来检查“实现”是否附和“期望”的,先写后写也都有这个用途。但是TDD强调写测试的时候只关心测试,实现的时候只关心实现,这样可以在两个阶段中集中精力把一件事做好。

    而且有句话叫做“不要把解决问题的方案,当成问题本身”--《你的灯亮着么?》
    先写代码再写测试,就很容易把对“要解决的问题”的测试,写成对“解决问题的方案”的测试。(这里要解决的问题,不一定是需求的问题,可能是写一个string.trim方法来去掉前后空格)最后测试验证的是“方案“本身是否符合你当初对”方案“的期望,而不是验证”方案“最终是否解决了”问题“,这个才是你真正的期望。

  145. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-10-16 14:31:00

    xiao_p:
    回头再看 winter-cn的“用比较细致的Test case代替概括性的Use case来描述需求”

    和Ivony在136楼的回复中的思想是多么的相似。

    我实在不明白为什么那么多人会要求winter-cn去弄本书看,替他打抱不平下。


    我确实去弄了本书看 而且觉得颇有收获

  146. 林肯[未注册用户]
    *.*.*.*
    链接

    林肯[未注册用户] 2009-10-16 14:57:00

    客户提出需求 -> 为需求准备测试方案 -> 试图通过测试 -> 在试图通过测试的过程中,提出新的更细化的需求 -> 准备新的细化的测试方案 -> 试图通过这些测试


    我觉得基本就是这样的一个过程.但是有几点我想说明一下.
    1.客户提出需求
    这个环节我们通过自己的BA和客户进行沟通,挖掘真实的需求, 掌控story的粒度,使story尽可能的简单
    2.为需求准备测试方案
    这个环节通常是我们每个迭代的IPM, PM BA DEV QA大家一起过一边story, 提出疑问, 统一认识.
    3.试图通过测试
    这个我可以认为是Function级的测试么? 假如是,那我所在过的两个项目,采取过不同的策略.
    第一个WPF项目,通常我们是在BA Sign Off story后, 由QA来写, 有时QA会和BA pair一起写, 有时QA会和DEV pair写, 有时她们自己pair写.
    为什么我们在Sign Off之后才写呢? 因为首先,经过BA的确认,至少story在功能性上不会有大的偏差.
    其次,因为这些测试是Function级的, 需要让它通过,只能等到story开发结束.
    在开发结束前,你都不能把已经写下的代码, check in到集成环境里, 因为它会break build.
    不能频繁快速的check in代码使我们很难受. 最后我们讨论决定, function 测试在Sign Off之后写, 但是DEV需要和BA保持畅通的交流.

    第二个RoR项目,我们也尝试过Story开发前, BA DEV QA确认理解一致后, DEV和QA Pair写Acceptance test.
    这里我们用Cucumber来写测试, 一个典型的Cucumber测试是这样的:

    GIVEN blah_blah_blan (test setup)
    When blah_operation_a
    AND blah_operation_b
    Then blah_blah (assertion)

    它已经足够简单,只描述一个简单的功能.而且它写完就可以check in到集成环境里.
    因为所有还没有实现的 blah的部分,跑测试的时候它不会失败,它会标记为黄色(not implemented yet)
    如果已经实现了, 能够正常执行它会标记为绿色(pass)
    但是执行出异常或者报错, 它才会标记为 红色 (failed)

    这样,一个cucumber 测试在集成环境里的输出也就是渐进式的了, 从全部黄色, 到一部分绿色, 到全部绿色.
    这个过程就是你说的试图通过测试的过程中, 提出新的更细化的需求.

    5.准备新的细化的测试方案
    如果说前面那个步骤,由QA来主导, 那么这个阶段就到了DEV来工作了, 进入我们的单元测试驱动的开发阶段.

  147. 林肯[未注册用户]
    *.*.*.*
    链接

    林肯[未注册用户] 2009-10-16 15:09:00

    而且有句话叫做“不要把解决问题的方案,当成问题本身”--《你的灯亮着么?》
    先写代码再写测试,就很容易把对“要解决的问题”的测试,写成对“解决问题的方案”的测试。(这里要解决的问题,不一定是需求的问题,可能是写一个string.trim方法来去掉前后空格)最后测试验证的是“方案“本身是否符合你当初对”方案“的期望,而不是验证”方案“最终是否解决了”问题“,这个才是你真正的期望。


    切中要害!

    PS.原来是你啊,我说怎么看着似曾相识呢.

  148. 戏水
    *.*.*.*
    链接

    戏水 2009-10-17 00:54:00

    路过2009:
    如果包包和老赵在同一个公司,同一个项目会怎么样???
    猜想中。。。
    哈哈


    这个很简单 ,他俩开始比发育 ……

  149. 戏水
    *.*.*.*
    链接

    戏水 2009-10-17 01:09:00

    我认为测试驱动开发 是更偏向于让客户得到实惠的一种方法学。
    测试驱动开发的目标是更快的交付可用的软件,什么叫可用? 满足了客户的需求叫做可用。对不对?

    之所以要以测试来驱动开发 是因为很多开发人员存在着一些问题。就是没有搞清楚需求之前,就动工了…… 然后写到一半的东西被主管说: 我靠 ,你这写的什么东东? 然后开发人员又不断的阉割、整形自己的代码,以“达到"主管提出的"要求" ,结果自然是生产了一个满目疮痍的软件出来。
    而先写测试的一个好处是迫使开发人员更加深刻的去了解需求,并且提供了可执行的检测方式。这种检测不但是检测 给定了某个输入 是否给出了预计的输出, 更重要的是给出了对满足客户需求的一种检测。

    而老赵提到的编写可测试的代码 和 测试驱动开发 的侧重点不同吧,前者更注重给开发人员提供实惠,毕竟自己写代码 冷暖自知。自己代码写得好,自己看着舒心,外人检察时放心,自己维护起来省心。

    花开两朵 各表一枝 。 花是两朵,根是一个。 over

  150. egmkang
    *.*.*.*
    链接

    egmkang 2009-10-17 01:40:00

    @路过2009
    一山不容二虎,这是定律.
    项目组里面有一个牛人,OK,些须会好一点;
    两个牛人一般只能拖慢项目进度.

  151. egmkang
    *.*.*.*
    链接

    egmkang 2009-10-17 01:48:00

    @林肯
    这句活说的很明白!!
    真乃高手

  152. egmkang
    *.*.*.*
    链接

    egmkang 2009-10-17 02:10:00

    花了将近一个小时看评论,收益匪浅,也证实我的思路是基本正确的.
    PS:
    博客园有很多高手,他们不写Blog.....

  153. asheng
    *.*.*.*
    链接

    asheng 2009-10-19 13:18:00

    包建强:
    你这篇文章真的有点扯蛋了,基本是发发牢骚,然后大概念套了一堆,一点技术含量都没有。
    之所以这么说,是因为我今天也写了一点在MVP中UnitTest的注意事项,也要涉及到TDD。但是我没发到首页上,因为我觉得自己列出的那10条,每一条都需要Sample支持。
    所以,你这篇文章杀了也罢,别忽悠初学者。

    还有上面那个Marlboro的回复,我很好奇,你学会了啥?


    ---------------------------------------------
    我就奇怪了,谁你都要喷几下
    你没有学到 东西 不代表别人没有从中悟到一些东西啊
    你“觉得”你了解深 你就从“技术”层面反驳 好不好
    别一上来 就说别人“扯蛋”
    这么多年了 怎么还是这么“愤青”呢(虽然你年纪比我大)
    人家 回复也关你啥事?
    我也从中 了解到了 别人,比如老赵 平时对TDD的理解 然后大家可以一起讨论一下嘛 怎么就不能学到东西呢?

  154. Yuanyi Wang
    *.*.*.*
    链接

    Yuanyi Wang 2009-10-31 23:15:00

    如果你能拿到一个有稳定需求的项目,敏捷和TDD都是不需要的,瀑布最好了;
    如果你预计需求获取阶段拿到的需求和验收时的可能会非常的不一致,敏捷是最好的了,常让用户看看,随时调整;
    如果你的需求经常的改变,还是TDD吧,否则回归测试的成本会拖死项目的。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我