如何使用TDD设计复杂系统?

12

类似于TDD是否意味着不考虑类设计?,我在思考传统“设计”阶段在TDD中的位置时遇到了困难。

根据保龄球游戏卡塔(“对话”版本,链接我暂时记不起来了),TDD似乎忽略了早期做出的设计决策(放弃帧对象、投掷对象等)。我能看出,在这个例子中,跟随测试并忽略您最初的设计思想是个好主意,但在更大的项目或希望留下扩展/定制空间的项目中,是否把您没有测试或立即不需要的东西插入其中会更好一些,以避免以后耗费时间重写?

简而言之——在进行TDD时,过多的设计是多少,我在编写测试和代码以通过它们(忽略我的设计只关注通过测试)时应该遵循多少设计?

还是说我在担心什么,如果您被逼入了一个死角,那么仅仅为了跟随测试而编写的代码是不难重写或重构的(在实践中)? 或者,我可能忽略了重点,即当我来测试新的功能部分时,我应该期待重写代码的部分吗?

4个回答

10

关于“设计大型复杂系统”应该注意的事情,不应与TDD联系在一起 - 特别是当TDD被解释为“测试驱动设计”而不是“测试驱动开发”时。

在“开发”的上下文中,使用TDD将确保您编写可测试的代码,从而获得有关TDD的所有优点(尽早检测错误,高代码:测试覆盖率,更容易进行未来重构等等)

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

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

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

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

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

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

我尚未遇到过采用测试驱动设计(TDD)风格完成的显著复杂软件(如编译器、数据库、操作系统)。以下博客文章极好地阐述了这一点 (Compilers, TDD, Mastery)

此外,查看以下关于架构的视频,这些视频为思考提供了很多常识。


实际上,我正在开发一个类似于vNext的Asp.Net MVC堆栈克隆版,但基于协程,并且我正在疯狂地尝试测试所有内容。实际上,我主要进行集成测试和单元测试,仅针对最小的组件,例如渲染或剃刀页面或路由处理程序。由于它也是多线程环境,我准备了一种“虚假线程”来逐步运行所有各个部分。我知道我没有进行严格的TDD,但在我看来,这对于“单人乐队”来说是不可能的。 - Kendar
到目前为止,这个问题的最明智的答案。TDD的倡导者们大喊大叫:“先写测试!”…但是…如果您没有任何东西,甚至对需要做什么都没有最基本的想法,那么您怎么能编写测试呢?您将编写正确的测试,但解决错误的问题或根本不解决任何需求!首先要了解问题领域,在Jupyter中编写原型(例如),评估其性能、内存占用、可扩展性和其他要求;如果需要,编写概念验证;然后利用在原型设计阶段获得的知识来使用TDD。 - Richard Gomes

10

我建议你基于你的初始设计来进行测试。TDD在很多方面都是一个发现过程。你可以期望要么确认你早期设计选择的正确性,要么找到更好的选择。尽可能多地进行前期设计,只要你感觉舒适。有些人喜欢飞行员式的高层次设计,并使用TDD来完善设计。而另一些人则更喜欢先将所有东西写在纸上。

TDD的一部分是重构。


3
对“TDD的一部分是重构”加1分:设计需要更新以反映在测试中发现的必要更改。 - Steven Evers
谢谢,那么我没有理解到重点,重构/重写是这种新兴设计的一部分。知道了很好,现在可以尝试一些东西了! - pete the pagan-gerbil

3

从一个粗略的设计想法开始,选择第一个测试并开始编码,一步步进行测试,让设计逐渐形成,与初始设计相似或不同。初始设计的多少取决于问题的复杂性。

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

严格遵循TDD和SOLID原则将带来干净、可测试和灵活的代码,以便可以轻松地进行重构,并利用单元测试作为脚手架防止回归。


那么,初始设计更多是为了防止紧急设计遇到瓶颈(或看起来即将如此)? - pete the pagan-gerbil
1
最初的设计是为了开始,为问题或问题的子部分提供第一个方向。设计是逐步创建的。 - philant
在没有任何想法的情况下“先写测试”最终会为错误的问题生成正确的测试。首先,必须了解问题领域和架构要求,因为我们正在讨论复杂的问题,然后使用小型原型来喂养您的想象力,并对这些想法进行审查。在大多数情况下,您只需要像IPython或Jupyter这样的原型工具,或者在更复杂的情况下需要一个概念验证,以验证企业需求。最后,编写代码并为其编写测试。 - Richard Gomes

2

我发现使用TDD有三种设计方式:

  • 随着重复和复杂性的消除,让设计自然地出现
  • 使用模拟对象结合单一职责原则首先创建完美的设计
  • 在实践中灵活选择

大多数情况下,实用主义似乎是最好的选择,所以这是我做的。如果我知道特定的模式非常适合我的问题(例如MVC),我会直接使用模拟对象并假设它有效。否则,如果设计不太清晰,我会允许它自行出现。

我觉得需要重构一个新兴的设计的交叉点是它变得不易更改的时候。如果一段代码设计并不完美,但另一个开发人员可以轻松重构它,那就足够了。如果代码变得如此复杂,以至于对另一个开发人员来说不再显而易见,那么就是重构的时候了。

我喜欢真实选项,并且将某些东西重构到完美状态感觉就像没有任何真正必要的情况下承诺设计。我会进行“足够好”的重构;这样,如果我的设计被证明是错误的,我就没浪费时间。假设如果你以前在类似的上下文中从未使用过它,那么你的设计将是错误的。

这也让我可以比完美更快地完成我的代码。话虽如此,正是我试图使代码完美的尝试教会了我如何找到界限!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接