【问题标题】:How do you design complex systems with TDD?您如何使用 TDD 设计复杂系统?
【发布时间】:2010-08-16 16:01:29
【问题描述】:

类似于Does TDD mean not thinking about class design?,我在思考传统“设计”阶段在哪里适合 TDD 时遇到了麻烦。

根据 Bowling Game Kata(“对话”版本,目前我没有看到它的链接),TDD 似乎忽略了早期做出的设计决策(丢弃框架对象、滚动对象等)。我可以在该示例中看到,遵循测试并忽略您的初始设计想法是一个好主意,但在更大的项目或您想要为扩展/定制留出空间的项目中,将东西放入会不会更好您没有测试或不需要立即进行测试以避免以后耗时的重写?

简而言之 - 在进行 TDD 时多少设计太多了,在我编写测试和通过测试的代码时我应该遵循多少设计(忽略我的设计只担心通过测试)?

或者我什么都不担心,如果你被画到角落里,仅仅为了遵循测试而编写的代码不是(在实践中)难以重写或重构? 或者,我是否错过了重点,并且我应该期望在我来测试新的功能部分时重写部分代码?

【问题讨论】:

    标签: unit-testing tdd


    【解决方案1】:

    我会将您的测试基于您的初始设计。在许多方面,TDD 是一个发现过程。您可以期望确认您的早期设计选择,或者发现您可以做出更好的选择。尽可能多地进行前期设计。有些人喜欢坐在椅子上做高级设计,并使用 TDD 来充实设计。而其他人则喜欢先把所有东西都写在纸上。

    部分 TDD 正在重构。

    【讨论】:

    • +1 表示“部分 TDD 正在重构”:需要更新设计以反映测试时发现的必要更改。
    • 谢谢 - 所以我没有抓住重点,重构/重写是这个紧急设计的一部分。很高兴知道,现在尝试一下!
    【解决方案2】:

    关于“设计大型复杂系统”有一些话要说,不应该与 TDD 相关联 - 尤其是,当 TDD 被解释为“测试驱动设计”而不是“测试驱动开发”时。

    在“开发”上下文中,使用 TDD 将确保您编写的代码可测试,这些代码提供了关于 TDD 的所有好处(及早检测错误、高代码:测试覆盖率、更容易的未来重构等. 等等)

    但在“设计”大型复杂系统时,TDD 并没有特别解决系统架构中固有的以下问题

    1. (工程)性能
    2. 安全性
    3. 可扩展性
    4. 可用性
    5. (以及所有其他“功能”)

    (即,上述所有问题都不会通过“首先编写失败的测试用例,然后是工作实现,重构 - 起泡,冲洗,重复......”配方神奇地“出现”)。

    • 对于这些问题,您需要根据需求和问题空间所施加的约束,通过白板系统的高级和低级详细信息来解决问题。

    • 上述一些考虑因素相互竞争,需要谨慎权衡,而这些因素不会通过编写大量单元测试而“出现”。

    • 一旦定义了关键组件及其职责并 明白了,TDD可以用在这些实现中 组件。重构和不断的过程 审查/改进您的代码将确保低级设计 这些组件的细节设计精良。

    我还没有遇到过以测试驱动设计风格完成的非常复杂的软件(例如编译器、数据库、操作系统)。以下博客文章非常好地谈到了这一点(Compilers, TDD, Mastery

    另外,请检查以下videos on Architecture,它为思考过程增加了很多常识。

    【讨论】:

    • 实际上,我正在开发一个 Asp.Net MVC 堆栈的克隆,类似于 vNext,但基于协程,我正在疯狂地尝试测试所有内容。实际上,我主要只针对最小的组件进行集成测试和单元测试,例如渲染或剃须刀页面或路由处理程序。作为一个多线程环境,我准备了一种“假线程”来逐步运行所有各个部分。我知道我没有做严格的 TDD,但恕我直言,对于“单人乐队”来说这是不可能的。
    • 到目前为止,这个问题最明智的答案。 TDD 的拥护者宣扬:“先写测试!”……但是……如果你什么都没有,甚至连需要做什么的想法都没有,你怎么能写测试呢?您将编写正确的测试来解决错误的问题或根本不解决任何需要的问题!首先了解问题域,例如在 Jupyter 内部编写一个原型,评估其性能、内存占用、可扩展性等要求;如果需要,写一份概念证明;然后用你在原型设计阶段获得的这些知识来拥抱 TDD。
    【解决方案3】:

    从一个粗略的设计理念开始,选择第一个测试并开始编码,一个接一个地进行绿色测试,让设计出现,与初始设计相似或不同。多少初始设计取决于问题的复杂性。

    必须专心聆听和嗅探代码,以检测重构机会和代码异味。

    严格遵循 TDD 和 SOLID principles 将带来代码简洁、可测试和灵活,因此可以轻松重构,利用单元测试作为脚手架来防止回归。

    【讨论】:

    • 所以初始设计更多是为了以防紧急设计遇到砖墙(或看起来好像即将发生)?
    • 最初的设计是开始,给出第一个方向,针对问题——或问题的子部分。设计是逐步创建的。
    • “先写测试”对需要做的事情一无所知,最终会为错误的问题生成正确的测试。首先有必要了解问题域和架构要求,因为我们正在谈论复杂的问题,然后使用小型原型来满足您的想象力,并仔细审查这些想法。在大多数情况下,您只需要一个原型工具,例如 IPython 或 Jupyter,或者在更复杂的情况下需要一个概念证明,旨在验证企业需求。最后,您编写代码并为其编写测试。
    【解决方案4】:

    我发现了三种使用 TDD 进行设计的方法:

    • 消除重复和复杂性后,让设计自然呈现
    • 使用结合单一职责原则的模拟来预先创建完美的设计
    • 要务实。

    实用主义似乎是大多数时候最好的选择,所以这就是我所做的。如果我知道一个特定的模式非常适合我的问题(例如,MVC),我会直接去模拟并假设它有效。否则,如果设计不太清楚,我会允许它出现。

    我觉得需要重构一个紧急设计的交叉点是它不再容易改变的点。如果一段代码设计不完美,但遇到它的另一个开发人员可以轻松地自己重构它,那已经足够了。如果代码变得如此复杂以至于对其他开发人员来说不再明显,那么是时候重构它了。

    我喜欢 Real Options,将某些东西重构到完美对我来说就像是在没有任何实际需要的情况下致力于设计。我改为重构为“足够好”;这样,如果我的设计证明自己是错误的,我就不会浪费时间。如果您以前从未在类似的环境中使用过它,那么假设您的设计将是错误的。

    这也让我可以比完美的代码更快地输出代码。话虽如此,正是我试图让代码变得完美,才让我明白了这条线在哪里!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-09-06
      • 1970-01-01
      • 2011-01-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多