【发布时间】:2008-09-17 05:20:05
【问题描述】:
我对 TDD 的看法很复杂。虽然我相信测试,但我对测试驱动我的开发工作的想法存在疑问。
当您编写代码以满足为满足您当前需求的接口编写的一些测试时,您可能会将注意力从构建可维护的代码、简洁的设计和健全的架构转移。
我遇到了驱动而不是测试的问题。有什么想法吗?
【问题讨论】:
标签: architecture tdd agile
我对 TDD 的看法很复杂。虽然我相信测试,但我对测试驱动我的开发工作的想法存在疑问。
当您编写代码以满足为满足您当前需求的接口编写的一些测试时,您可能会将注意力从构建可维护的代码、简洁的设计和健全的架构转移。
我遇到了驱动而不是测试的问题。有什么想法吗?
【问题讨论】:
标签: architecture tdd agile
没有。
如果做得好,测试驱动开发就是你的设计工具。
我希望您原谅我链接到 my own blog entry, wherein I discuss the pitfalls of Test Driven Development that went wrong 只是因为开发人员将他们的测试仅仅视为测试。
在之前的项目中,开发人员使用了一种极具破坏性的单例模式,该模式在整个项目中强制执行依赖关系,当需求发生变化时,这只会破坏整个事情:
TDD 被视为一项任务,当它 应该被视为一个 方法。 [...]
无法识别 TDD 不是关于测试,而是 关于设计。猖獗的案例 单元测试中的单例滥用 这很明显:而不是测试 作家认为“WTF是这些 单身=价值;在做的陈述 我的测试?”,测试作者只是 将单例传播到 测试。 330 次。
不幸的后果是 构建服务器强制测试是 让它过去,无论如何。
测试驱动开发,做得对,应该让开发人员高度意识到设计缺陷,如紧耦合、违反 DRY(不要重复自己)、违反 SRP(单一责任原则)等。
如果你为了通过测试而编写通过测试的代码,那么你已经失败了:你应该将难以编写测试视为路标,让你问:为什么要这样做方式?为什么我不能在不依赖其他代码的情况下测试此代码?为什么我不能重用这段代码?为什么这个代码在单独使用时会中断?
如果您的设计真正干净,并且您的代码真正可维护,为什么为它编写测试并非易事?
【讨论】:
TDD 设计或前期设计总是存在过度设计的风险。所以答案是视情况而定。我更喜欢从用户故事/验收测试开始,这是我的测试将有助于生产的要求的基础。只有在我确定了这一点之后,我才开始编写详细的单元测试 TDD 风格。如果您所做的唯一设计和思考是通过 TDD,那么您会冒太多的自下而上方法的风险,这可能会给您提供出色的独立单元和类,但是当您尝试将它们集成到用户故事完成任务中时可能会因为做错了这一切而感到惊讶。有关这方面的更多灵感,请查看BDD。
A great "debate" about this has been recorded 介于 Robert C. Martin 和 James Coplien 之间,前者是 TDD 倡导者,后者声称它破坏了系统的设计。这就是 Robert 关于 TDD 和设计的说法:
“敏捷中有一种感觉 自 99 年左右以来的社区 建筑是无关紧要的,我们不 需要做建筑,我们需要的一切 要做的是写很多测试然后做 很多故事,做的很快 迭代和代码将组装 本身神奇,这一直 是马屎。我什至认为大部分 最初的敏捷支持者会 同意那是愚蠢的。”
James Coplien 指出,仅仅从 TDD 驱动您的设计具有很大的风险:
“我们经常看到的东西之一,在 很多项目,是项目去 在他们的第三次冲刺和 他们崩溃和燃烧,因为他们 不能再进一步了,因为他们 已经走投无路 在建筑上。而你不能 重构你的出路,因为 重构必须跨类 类别,跨类层次结构, 你再也不能拥有任何 关于拥有相同的保证 功能。”
此外,他还举了一个很好的例子,说明如果您试驾银行账户与使用您的前期知识来推动架构相比,银行账户的外观可能会如何:
“我记得我和我说话的时候 肯特一次,大约在早期 当他提出 TDD 时,这 在 YAGNI 的意义上,在做 最简单的事情可能 工作,他说:‘好吧。让我们做一个 银行账户,储蓄账户。 什么是储蓄账户?它是 号码,你可以添加到号码 你可以从数字中减去。 那么储蓄账户是什么 计算器。让我们做一个计算器, 我们可以证明您可以添加到 余额和减去 平衡。这是最简单的事情 这可能会奏效,一切 else 是它的演变。
如果你做一个真正的银行系统,一个 储蓄账户甚至不是一个对象 你不会重构你的 通往正确架构的方式 那个。储蓄账户是什么, 是一个进行迭代的过程 通过数据库的审计跟踪 存款和利息的交易 聚会和其他轮班 钱。这不像储蓄 帐户是一些钱坐在 某处银行的架子上,即使 那是用户的视角,并且 你只需要知道有 这些相对复杂的结构 在银行系统的基础上 支持税收人民和 精算师和所有其他人, 你无法到达的 增量方式。嗯,你可以, 因为当然是银行业 40年后才走到这一步。你 想给自己40年?它是 不敏捷。”
这里有趣的是,TDD 支持者和 TDD 反对者都说您需要预先设计。
如果您有时间,请观看视频。这是两位极具影响力的专家之间的一场精彩讨论,时长仅为 22 分钟。
【讨论】:
我完全同意 pjz。没有一种设计软件的正确方法。如果您将 TDD 发挥到极致,除了下一个单元测试之外没有任何预先考虑,您可能会让事情变得更加困难。对于那些通过花费数月时间在图表和文档上但没有代码而着手进行大型软件项目的人也是如此。
适中。如果有绘制快速图表的冲动,可以帮助您可视化代码结构,那就去做吧。如果您需要两页,可能是时候开始编写一些代码了。如果您想在编写测试之前这样做,那又如何。目标是工作、高质量的软件,而不是绝对符合任何特定的软件开发原则。做对你和你的团队有用的事情。寻找可以改进的地方。迭代。
【讨论】:
在这个问题上我完全同意你的看法。在实践中,我认为 TDD 经常对代码库产生一些非常负面的影响(蹩脚的设计、过程代码、没有封装、生产代码中充斥着测试代码、到处都是接口、难以重构生产代码,因为一切都是与许多测试等紧密耦合)。
Jim Coplien 已经就这个话题发表了一段时间的演讲:
最近的研究(Siniaalto 和 Abrahamsson) 的 TDD 表明它可能 与传统相比没有任何优势 测试最后的开发,在某些情况下 案件已经恶化了代码和 它还有其他令人担忧的(他们的 字)效果。让我担心的那个 最多的是它会恶化 建筑学。 --Jim's blog
Robert C. Martin 和 James Coplien 之间还有一个discussion over on InfoQ,他们在其中谈到了这个主题。
【讨论】:
我的想法是,首先编写您希望代码看起来像的样子。 一旦你有了目标代码的样本(现在什么都不做),看看你是否可以在上面放置一个测试脚手架。 如果你做不到,找出你做不到的原因。 大多数情况下,这是因为您做出了错误的设计决定 (99%),但如果情况并非如此 (1%),请尝试以下操作:
在您拥有目标代码和测试脚手架之后。实现代码。现在,您甚至可以在通过自己的测试时了解自己的进步情况(这是一个很好的动力!)
根据个人经验,测试可能是多余的唯一情况是,您正在制作早期原型,因为那时您还没有充分理解问题,无法准确地设计或测试您的代码。
【讨论】:
这里有许多非正式的意见,包括流行的意见(来自 Jon Limjap),即错误的结果源于错误的做法,以及似乎仅靠个人经验没有支持的说法。大量的经验证据和已发表的结果指向与该经验相反的方向。
理论是要求您在代码之前编写测试的方法将导致在单个代码片段级别考虑设计 - 即小型编程。由于程序是您可以测试的全部(您仍然一次测试一种方法,并且您根本无法测试大多数语言中的类),您的设计重点是各个方法以及它们的组合方式。从理论上讲,这会导致自下而上的程序设计,进而导致对象之间的不良耦合和内聚。
广泛的经验数据证实了这一理论。 Sniaalto 和 Abrahamsson,(Comparative Case Study on the Effect of Test-Driven Development on the Effect of Program Design and Test Coverage),ESEM 2007,发现“我们的结果表明凝聚力可能更差(尽管 Beck声称 TDD 产生了高度内聚的系统)。在我们的第二项研究中,我们注意到 TDD 的复杂性度量更好,但依赖管理指标明显更差。 Janzen 和 Saledian(Does Test-Driven Development 难道真的提高了软件设计质量吗? IEEE Software 25(2), March/April 2008, pp. 77 - 84)发现“[T]he results didn't t 支持降低耦合和增加与 TDD 的内聚力的主张”。
文献综述将发现其他出版物进一步推动这些案例。
即使是我亲爱的朋友 Bob 叔叔也写道:“敏捷开发中一个更阴险、更持久的神话是,前期架构和设计是不好的;你永远不应该花时间预先做出架构决策。相反,你应该“从无到有发展你的架构和设计,一次一个测试用例。请原谅我,但那是马屎。” (“敏捷架构的分类学”, 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。
【讨论】:
完成软件分为三个步骤:
测试让你成为第一。您的代码不会因为测试通过而完成。在开始编写测试/代码之前,最好有一些项目结构的概念(实用程序、常用对象、层、框架)。在编写代码以使测试通过之后,您需要重新评估它以查看哪些部分可以重构到应用程序的不同方面。 Yuo 可以自信地做到这一点,因为您知道,只要您的测试仍然通过,您的代码仍然可以正常工作(或至少满足要求)。
在项目开始时,考虑结构。随着项目的进行,继续评估和重新评估您的代码以保持设计到位或在设计不再有意义时更改设计。估算时必须考虑所有这些项目,否则最终会得到意大利面条代码,TDD 与否。
【讨论】:
始终保持平衡:
- 过多的 TDD,你最终得到的代码可以工作,但工作起来很痛苦。
- 太多“可维护的代码、简洁的设计和健全的架构”,你最终会得到Architecture Astronauts,他们自言自语到编码瘫痪
凡事适度。
【讨论】:
我对 TDD 和单元测试比较陌生,但是在我使用它的两个副项目中,我发现它是一种设计助手,而不是设计的替代品。独立测试和验证组件/子组件的能力使我更容易进行快速更改和尝试新的设计理念。
我在 TDD 中体验到的不同之处在于可靠性。在设计过程开始时(而不是稍后)在较小级别的组件上制定组件接口的过程是,我有我可以信任的组件会在更早工作,所以我可以不用担心关于小部分,而是着手解决棘手的问题。
当我不可避免地需要回来维护小部件时,我可以花更少时间这样做,这样我就可以回到我想做的工作。
【讨论】:
在很大程度上,我同意 TDD 确实提供了一种设计工具。对我来说最重要的部分是它建立了进行更多更改的能力(你知道,当你有瞬间的洞察力时,你可以通过删除代码来添加功能)大大降低了风险。
也就是说,我最近承包的一些算法工作在没有仔细平衡设计思想的情况下在 TDD 下受到了一些影响。上面关于更安全的重构的声明仍然是一个很大的好处,但是对于某些算法,TDD(尽管仍然有用)不足以让您获得理想的解决方案。以排序为例。 TDD 很容易导致您使用次优 (N^2) 算法(以及允许您重构为快速排序的大量通过测试),例如冒泡排序。 TDD 是一个工具,一个非常好的工具,但就像很多东西一样,需要根据正在解决的问题的上下文来适当地使用。
【讨论】: