我读了最新的coding horror post,其中一条评论让我感到很紧张:
这是测试驱动设计/重构应该修复的情况类型。如果(很大的话)你有接口测试,重写实现是没有风险的,因为你会知道你是否抓住了所有东西。
现在理论上我喜欢测试驱动开发的想法,但是我一直尝试使它工作,它没有特别好,我摆脱了习惯,接下来我知道所有的我最初写的测试不仅没有通过,而且它们不再是系统设计的反映。
如果你从一开始就从高处直接交给一个完美的设计(根据我的经验从未实际发生过),这一切都很好,但如果你在系统的制作过程中注意到有一个设计中的关键缺陷?然后它不再是潜入和修复“bug”的简单问题,但你还必须重写所有的测试。一个基本假设是错误的,现在你必须改变它。现在,测试驱动的开发不再是一个方便的东西,但它只是意味着完成所有工作的工作量是其两倍。
我之前尝试过这个问题,包括同行和在线,但我从来没有听过一个非常令人满意的答案。 ......哦等等......问题是什么?
如何将测试驱动开发与必须更改的设计相结合,以反映对问题空间日益增长的理解?你如何让TDD练习为你而不是对你有用?
更新 我仍然不认为我完全理解这一切,所以我无法真正决定接受哪个答案。我的大部分理解都发生在评论部分,而不是答案中。这是迄今为止我最喜欢的一系列作品:
“使用”无风险“等字词的任何人 在软件开发方面确实很充实 狗屎。但是不要只是注销TDD 因为它的一些支持者是 超级易受炒作。我找到了 帮助我澄清我的想法 写一大堆代码,帮助我 重现错误并修复它们,并制作 我对重构更有信心 他们开始看起来丑陋的事情“
-Kristopher Johnson
“在这种情况下,您重写测试 仅用于界面的各个部分 已经改变,并考虑 你自己很幸运,有很好的考验 报道其他地方的报道 其他对象依赖于它。“
-rcoder
“在TDD中,写测试的原因 是做设计。之所以要做 自动化测试是可以的 将它们重用为设计和代码 发展。当测试中断时,就意味着 你以某种方式违反了早先的规定 设计决定。也许这是一个 决定你想改变,但它是 很高兴尽快得到反馈 可能的“。
-Kristopher Johnson
[关于测试接口]“测试会插入一些元素, 检查尺寸是否对应 插入的元素数量,检查 contains()为它们返回true 但不是没有的东西 插入,检查remove()是否有效, 所有这些测试都是 所有实现都相同,并且 当然你会运行相同的代码 对于每个实现而不是复制 它。所以当界面发生变化时 你只需要调整测试 代码一次,而不是每次一次 实施“。
-Michael Borgwardt
答案 0 :(得分:3)
TDD的一个实践是使用婴儿步骤(在开始时可能非常无聊),这是使用非常小的步骤,以便您了解您的问题空间,并为一个良好和令人满意的解决方案你的问题。
如果您已经了解应用程序的设计,那么您根本就不会使用TDD。我们应该在进行测试时进行设计。
所以我给出的建议是让你专注于婴儿步骤,以便获得适当的可测试设计
答案 1 :(得分:1)
我认为TDD的任何真正的实践者都不会声称它完全消除了错误或回归的可能性。
请记住,TDD基本上是关于设计,而不是关于测试或质量控制。说“我所有的测试都通过”并不意味着“我已经完成了。”
如果您的要求或高级设计发生了巨大变化,那么您可能需要丢弃所有测试以及所有代码。这就是有时候的事情。这并不意味着TDD没有帮助你。
答案 2 :(得分:1)
正确应用,TDD实际上应该在面对不断变化的需求时让您的生活更轻松。
根据我的经验,易于测试的代码是与其他子系统正交的代码,并且具有明确定义的接口。鉴于这样一个起点,重写应用程序的重要部分要容易得多,因为您可以放心地工作,因为a)您的更改将被隔离到几个子系统,并且b)任何破坏都会很快显示为失败的测试
另一方面,如果您只是在设计完成后对代码进行单元测试,那么当需求发生变化时,您可能会遇到问题。当子系统发生变化(因为它们有效地标记了回归)和那些脆弱的测试时,快速失败的测试之间存在差异,因为它们依赖于太多不相关的系统状态。前者应该可以通过几行代码来修复,而后者可能会让你长时间试图解开它们。
答案 3 :(得分:1)
唯一真正的答案是取决于。
软件工具和专家市场的一个奇怪的怪癖是,为了最大化推动他们的人的收入,他们总是被写成好像他们以某种方式适用于“所有软件”。
事实是,'软件'与'硬件'一样多样化,没有人会想到买一本关于桥梁制作的书来设计电子设备或建造花园棚。
答案 4 :(得分:1)
我认为你对TDD有一些误解。有关它是什么以及如何使用它的一个很好的解释和示例,我建议阅读Kent Beck的Test-Driven Development: By Example。
以下是一些进一步的评论,可以帮助您了解TDD是什么以及为什么有些人会发誓:
“您如何将测试驱动开发与必须改变的设计相结合,以反映对问题空间日益增长的理解?”
“你如何让TDD练习为你而不是对你有用?”
TDD不是“不做TDD工作的两倍”。是的,你会写很多测试,但这并不需要花费太多时间,而且不会浪费精力。你必须以某种方式测试你的代码,对吧?每当您更改内容时,运行自动化测试比手动测试要快得多。
许多TDD教程都提供了每个类的每个方法的高度详细的测试。在现实生活中,人们不会这样做。为每个setter,每个getter等编写一个测试是愚蠢的。 Beck书很好地展示了如何使用TDD快速设计和实现某些东西,只有当事情变得棘手时才会放慢到“婴儿步骤”。有关此问题的更多信息,请参阅How Deep Are Your Unit Tests。
TDD与回归测试无关。 TDD是在编写代码之前考虑的。但是进行回归测试是一个很好的附带好处。他们不保证代码永远不会破坏,但它们有很多帮助。
当您进行导致测试破坏的更改时,这不是一件坏事;这是有价值的反馈。设计确实发生了变化,你的测试也不是一成不变的。如果您的设计发生了很大变化,以至于某些测试不再有效,那么就把它们扔掉。编写您需要对新设计充满信心的新测试。
答案 5 :(得分:0)
这不再是一件简单的事了 潜入并修复“虫子”,但是 你还必须重写所有的 测试
TDD的基本信条是避免生产代码和测试代码中的重复。如果单个设计更改意味着您必须重写所有内容,那么您没有进行TDD(或者根本没有正确执行)。
理想情况下,在设计良好且关注度正确分离的系统中,设计更改是本地的,就像实现更改一样。虽然现实世界很少是理想的,但你通常仍会得到一些东西:你必须改变生产代码的一些和一些测试,但不是一切,而且变化大多是简单的,可能甚至可以通过重构工具自动完成。
答案 6 :(得分:0)
持续集成(CI)是一个关键。如果每次签入源代码控制时都会自动运行测试(如果其他人失败则会看到其他人),那么更容易避免“陈旧”测试并保持绿色。
正如迪亚斯先生所说,婴儿步骤非常重要。你做了一个小的重构,你运行你的测试。如果测试中断,您可以立即确定是否预期(设计更改)或重构失败。当测试真正独立时(伴随练习),这很难实现。慢慢地进化你的设计。
另见http://thought-tracker.blogspot.com/2005/11/notes-on-pragmatic-unit-testing.html - 绝对买书!
编辑:也许我正在以错误的方式看待这个问题。假设您有一个想要重新设计的遗留代码库。我要做的第一件事是为当前行为添加测试。没有测试的重构是有风险的 - 您可能会改变行为。在那之后,我将开始清理设计,只需几步,在每一步之后运行我的单元测试。这会让我相信我的改变并没有破坏任何东西。在某些时候,API可能会发生变化。这将是一个重大改变 - 客户必须更新。测试会告诉我 - 这很好,因为我必须更新任何现有的客户端(包括测试)。
现在不是TDD。但是这个想法是一样的 - 测试是行为规范(是的,我正在考虑BDD),他们让我有信心重构实现,同时保证我保留行为(以及让我知道什么时候我改变界面。)
在实践中,我发现TDD可以立即反馈糟糕的界面设计。我是我的第一个客户 - 我知道我的API难以使用。
答案 7 :(得分:0)
在不知道什么在UI中最有效的同时编写内容,同时编写单元测试。这非常耗时。最好先制作一些GUI原型来使交互正确..然后用unittests重写它(如果雇主允许的话)。
答案 8 :(得分:0)
我们倾向于使用TDD做很多较少设计,知道它可以改变。我通过巨大的旋转采取了项目(这是一个网络应用程序,不是它是一个RESTful服务器,不是它是一个机器人)。这些测试使我能够比未经测试的代码更轻松地重构和重构代码并进化代码。尽管看似矛盾,但事实确实如此 - 即使您拥有更多代码,您也可以进行重大更改,并且充满信心在现有功能中没有任何损失。
我理解你担心基本的假设改变会让你抛出测试。这似乎很直观,但我个人还没有看到它。有些测试会进行,但大多数测试仍然有效 - 通常一个重大变化并不像最初看起来那么重要。另外,随着你在编写测试时越来越好,你倾向于写出不那么脆弱的测试,这会有所帮助。