我对 TDD 有复杂的感觉。虽然我相信测试,但我对测试驱动我的开发工作的想法有疑问。
当您编写代码以满足为满足您现在的需求的接口编写的一些测试时,您可能会将注意力从构建可维护的代码、简洁的设计和健全的架构转移。
我在驱动而不是测试方面遇到问题。有什么想法吗?
我对 TDD 有复杂的感觉。虽然我相信测试,但我对测试驱动我的开发工作的想法有疑问。
当您编写代码以满足为满足您现在的需求的接口编写的一些测试时,您可能会将注意力从构建可维护的代码、简洁的设计和健全的架构转移。
我在驱动而不是测试方面遇到问题。有什么想法吗?
不。
如果做得对,测试驱动开发就是您的设计工具。
我希望你原谅我链接到我自己的博客条目,其中我讨论了测试驱动开发的错误,因为开发人员仅仅将他们的测试视为测试。
在之前的项目中,开发人员使用了一种极具破坏性的单例模式,该模式在整个项目中强制实施依赖关系,当需求发生变化时,这只会破坏整个事情:
TDD 被视为一项任务,而它本应被视为一种方法。[...]
没有认识到 TDD 不是关于测试,而是关于设计。单元测试中滥用单例的猖獗案例使这一点显而易见:而不是测试编写者认为“WTF 是这些单例 = 值;语句在我的测试中做什么?”,测试作者只是将单例传播到测试中。330次。
不幸的结果是构建服务器强制测试通过了,不管它采取什么措施。
正确的测试驱动开发应该让开发人员高度意识到设计缺陷,如紧耦合、违反 DRY(不要重复自己)、违反 SRP(单一责任原则)等。
如果你为了通过测试而为测试编写通过代码,那么你已经失败了:你应该把努力编写测试当作路标,让你问:为什么要这样做?为什么我不能在不依赖其他代码的情况下测试此代码?为什么我不能重用这段代码?为什么这个代码在单独使用时会中断?
此外,如果您的设计是真正干净的,并且您的代码是真正可维护的,为什么要为它编写测试并非易事?
TDD 设计或前期设计总是存在过度设计的风险。所以答案是视情况而定。我更喜欢从用户故事/验收测试开始,这是我的测试将有助于生产的要求的基础。只有在我确定了这一点之后,我才开始编写详细的单元测试 TDD 风格。如果您所做的唯一设计和思考是通过 TDD,那么您会冒太多的自下而上方法的风险,这可能会给您提供出色的独立单元和类,但是当您尝试将它们集成到用户故事完成任务中时可能会因为做错了这一切而感到惊讶。有关这方面的更多灵感,请查看BDD。
Robert C. Martin 和 James Coplien 之间就此事进行了一场激烈的“辩论” ,前者是 TDD 的倡导者,后者声称它破坏了系统的设计。这就是 Robert 关于 TDD 和设计的说法:
“自 99 年左右以来,敏捷社区一直有一种感觉,即架构是无关紧要的,我们不需要做架构,我们需要做的就是编写大量测试、编写大量故事并进行快速迭代,然后”
James Coplien 指出,仅仅从 TDD 驱动您的设计具有很大的风险:
“在很多项目中,我们经常看到的一件事是,项目在第三次冲刺时向南走,它们崩溃和燃烧,因为它们不能再进一步了,因为它们在建筑上已经走投无路了。你不能重构你的出路,因为重构必须跨类类别,跨类层次结构,你不能再保证拥有相同的功能。”
此外,他还举了一个很好的例子,说明如果您试驾了银行账户,与使用您的前期知识来推动架构相比,银行账户的外观可能会如何:
“我记得有一次我和 Kent 谈过,在他提出 TDD 的早期,这是在 YAGNI 的意义上,做最简单的可能可行的事情,他说:‘好吧。让我们做一个银行账户,储蓄账户。什么是储蓄账户?它是一个数字,可以加数字,也可以减去数字。那么储蓄账户是什么,是一个计算器。让我们做一个计算器,我们可以证明你可以加到余额中并从余额中减去。这是可能可行的最简单的事情,其他一切都是它的演变。
如果你做一个真正的银行系统,一个储蓄账户甚至不是一个对象,你不会从那个重构到正确的架构。储蓄账户是一个对数据库交易、存款和利息收集以及其他资金转移的审计跟踪进行迭代的过程。这不像储蓄账户是银行某处货架上的一些钱,即使这是从用户的角度来看,你只需要知道在银行系统的基础中有这些相对复杂的结构需要支持税务人员和精算师以及所有其他人,您无法以渐进的方式获得。好吧,你可以,因为银行业当然是在 40 年后走到了这一步。你想给自己40年吗?它不灵活。”
有趣的是,TDD 的支持者和 TDD 的反对者都说你需要预先设计。
如果您有时间,请观看视频。这是两位极具影响力的专家之间的一场精彩讨论,只有 22 分钟。
我完全同意 pjz。没有一种设计软件的正确方法。如果您将 TDD 推向极端,除了下一个单元测试之外没有任何预先考虑,您可能会让自己变得更难。对于那些通过花费数月时间在图表和文档上但没有代码而着手进行大型软件项目的人也是如此。
缓和。如果想绘制一个快速图表来帮助您可视化代码的结构,那就去做吧。如果您需要两页,可能是时候开始编写一些代码了。如果你想在编写测试之前这样做,那又怎样。目标是工作、高质量的软件,而不是绝对符合任何特定的软件开发原则。做对你和你的团队有用的事。寻找可以改进的地方。迭代。
在这个问题上我完全同意你的看法。在实践中,我认为 TDD 经常对代码库产生一些非常负面的影响(蹩脚的设计、过程代码、没有封装、生产代码中充斥着测试代码、到处都是接口、难以重构生产代码,因为一切都与许多测试紧密耦合等。 )。
Jim Coplien已经就这个话题发表了一段时间的演讲:
最近对 TDD 的研究(Siniaalto 和 Abrahamsson)表明,与传统的后测试开发相比,它可能没有任何好处,并且在某些情况下会恶化代码,并且它具有其他令人担忧的(他们的话)效果。最让我担心的是它会破坏架构。——吉姆的博客
Robert C. Martin 和 James Coplien 之间也有关于 InfoQ 的讨论,他们谈到了这个主题。
我的想法是,首先编写您希望代码看起来像的样子。一旦你有了目标代码的样本(现在什么都不做),看看你是否可以在上面放置一个测试脚手架。如果你做不到,找出你做不到的原因。大多数情况下,这是因为您做出了错误的设计决定 (99%),但如果不是这种情况 (1%),请尝试以下操作:
在您拥有目标代码和测试脚手架之后。实现代码。现在,您甚至可以在通过自己的测试时知道自己的进步情况(这是一个很好的动力!)
从个人经验来看,测试可能是多余的唯一情况是,当您制作早期原型时,因为那时您还没有充分理解问题,无法准确地设计或测试您的代码。
完成软件分为三个步骤:
测试让你#1。您的代码不会因为测试通过而完成。在开始编写测试/代码之前,最好有一些项目结构的概念(实用程序、常用对象、层、框架)。在编写代码以使测试通过之后,您需要重新评估它以查看哪些部分可以重构到应用程序的不同方面。Yuo 可以自信地做到这一点,因为您知道只要您的测试仍然通过,您的代码仍然可以正常工作(或至少满足要求)。
在项目开始时,考虑结构。随着项目的进行,继续评估和重新评估您的代码,以保持设计到位或在设计不再有意义时更改设计。估算时必须考虑所有这些项目,否则最终会得到意大利面条代码,TDD 与否。
这里有许多非正式的意见,包括流行的意见(来自 Jon Limjap),即错误的结果来自于做错,以及似乎只有个人经验支持的说法。优势经验证据和已发表的结果指向与该经验相反的方向。
该理论认为,一种要求您在代码之前编写测试的方法将导致在单个代码片段的级别上考虑设计——即小型编程。由于程序是您可以测试的全部(您仍然一次测试一种方法,并且您根本无法测试大多数语言中的类),您的设计重点是各个方法以及它们的组合方式。从理论上讲,这会导致自下而上的程序设计,进而导致对象之间的不良耦合和内聚。
广泛的经验数据证实了这一理论。Sniaalto 和 Abrahamsson,(关于测试驱动开发对程序设计和测试覆盖的影响的比较案例研究),ESEM 2007,发现“我们的结果表明内聚可能更差(尽管贝克声称 TDD 产生了高度内聚的系统) ). 在我们的第二项研究中,我们注意到 TDD 的复杂性度量更好,但依赖管理度量显然更差。Janzen 和 Saledian(测试驱动开发真的提高了软件设计质量吗? IEEE 软件 25(2),2008 年 3 月/4 月,第 77 - 84 页)发现“[T] 结果不支持关于低耦合和增加与 TDD 的凝聚力”。
文献综述将发现其他出版物进一步推动这些案例。
甚至我亲爱的朋友鲍勃叔叔也写道:“敏捷开发中一个更阴险、更持久的神话是前期架构和设计是不好的;你永远不应该花时间预先做出架构决策。相反,你应该发展你的架构“从零开始设计,一次一个测试用例。对不起,那是马屁。” (“敏捷架构的 Scatology”, http://blog.objectmentor.com/articles/2009/04/25/the-scatology-of-agile-architecture)
然而,值得注意的是,更广泛的失败在于人们认为它是一种测试技术,而不是一种设计技术。Osherov 指出了许多通常被随便等同于 TDD 的方法。我不确定这里的海报是什么意思。请参阅:http ://weblogs.asp.net/rosherove/archive/2007/10/08/the-various-meanings-of-tdd.aspx 。
它始终是一种平衡:
- 过多的 TDD,你最终得到的代码可以工作,但工作起来很痛苦。
- 太多“可维护的代码、简洁的设计和健全的架构”,最终你会遇到让自己陷入编码瘫痪的架构宇航员
凡事适度。
我对 TDD 和单元测试比较陌生,但是在我使用它的两个副项目中,我发现它是设计助手而不是设计的替代品。独立测试和验证组件/子组件的能力使我更容易进行快速更改和尝试新的设计理念。
我在 TDD 中体验到的不同之处在于可靠性。在设计过程开始时而不是以后在较小级别的组件上解决组件接口的过程是,我有我可以相信的组件会更早地工作,所以我可以不再担心小块,而是得到致力于解决棘手的问题。
当我不可避免地需要回来维护小部件时,我可以花更少的时间来做这件事,这样我就可以回到我想做的工作上。
在大多数情况下,我同意 TDD 确实提供了一种设计工具。对我来说最重要的部分是它建立了进行更多更改的能力(你知道,当你有瞬间的洞察力时,你可以通过删除代码来添加功能),同时大大降低了风险。
也就是说,我最近承包的一些算法工作在没有仔细平衡设计思想的情况下在 TDD 下受到了一些影响。上面关于更安全的重构的声明仍然是一个很大的好处,但是对于某些算法,TDD(尽管仍然有用)不足以让您获得理想的解决方案。以排序为例。TDD 很容易导致您使用次优 (N^2) 算法(以及允许您重构为快速排序的大量通过测试),例如冒泡排序。TDD 是一个工具,一个非常好的工具,但就像许多东西一样,需要根据正在解决的问题的上下文来适当地使用它。