TDD是顶部还是底部设计?

时间:2011-04-18 07:31:22

标签: tdd

在我的记忆中,大多数人告诉我,我应该从上到下进行设计。如果我想实现一个网页,我应该在纸上绘制或绘制这个页面,然后将其分成一些功能。对于每个功能,我尝试设计外部API,并分别实现它们的内部。

但在TDD中,他们说我应该考虑一个非常小的功能(一种方法?),首先编写测试,失败,实现它并通过测试。编写它们是最后一步。我无法想象它如何获得良好的API。

最奇怪的是,他们说TDD不仅是单元测试,还有功能测试。我认为这意味着自上而下。如果存在由方法B,C和D组成的功能A.由于TDD,我首先为A编写功能测试。但是...... B,C,D都是未实现的。我应该使用三个存根吗?如果B依赖于其他三种方法?

我用TDD写了一些小程序。但是当我使用GUI攻击应用程序时,我陷入了困境。

3 个答案:

答案 0 :(得分:2)

由于TDD从你可以从外面看到的东西(你目前正在处理的任何项目)开始,我不确定它是否有资格作为自下而上。

将TDD推向极端(例如,在XP中,也就是极端编程),您肯定会从最终用户的角度出发,并且只编写尽可能多的代码来传递到目前为止创建的测试。如果你发现自己开始使用某些内部函数的测试,然后才能达到更高级别的测试(加上你编写的代码的良好设计以使这些测试通过)需要这个例程,那么你正在研究其他一些范例,不严格的TDD - 因为没有测试告诉你首先编写该方法。并不是说这一定是件坏事,但是你遇到的任何问题都不是TDD方法论的真正原因之一。

对于GUI编程,当然,即使在创建代码之前,您也有自动化测试的标准问题。我只知道Web应用程序的好工具;如果你在桌面案例中对这个主题有很好的指导,我很乐意看到它们。

答案 1 :(得分:0)

我一直在为我的rails项目编写rspec测试,在代码/测试之间保持大约1:1.6的比例。我从来没有真正解决过先写什么或者依赖什么的问题。如果我想写的方法A由B和C组成,那么我将首先使用正确的测试来实现B和C.对我来说,只要测试是好的和精确的,序列就不那么重要了。

所以,我并没有像你描述的那样使用存根,但只有当功能已经存在并且我只是想绕过/绕过它时。

顺便说一下,它被认为是一种自上而下的方法。这是Rspec Book的摘录:

  

如果您询问有经验的软件   交付人员为什么他们运行一个项目   像这样,前面加载所有   规划和分析,然后进入详细的设计和   编程,最后只是真正集成和测试它们   看着远处凝视远方   比他们年龄大,耐心   解释这是为了缓解   反对指数成本   改变 - 引入的原则   改变或发现缺陷   成倍地变得更加昂贵   你发现它的后来。该   自上而下的方法似乎是唯一的   明智的对冲方式   发现缺陷的可能性   当天晚些时候。

答案 2 :(得分:0)

我会说这是自上而下的。假设我有一个PDF,里面有4个不同的文档,我正在编写软件将它们分成4个单独的文档而不是单个文档,我可能会写的第一个测试是:

// Note: Keeping this very abstract
@Test
public void splitsDocumentsAccordingToDocumentType() {
   List docs = new DocumentProcessor().split(new SinglePdfWithFourDocs());
   assertEquals(4, docs());
   ...
}

我认为DocumentProcessor.split()方法与示例中的“A”类似。我现在可以在单个方法split中实现整个算法并使测试通过。我甚至不需要“B”或“C”对吗?知道自己是一名优秀的开发人员并且在考虑1500线方法时感到畏缩,您将开始寻找将代码重构为更合适的设计的方法。也许您看到可以从这段代码中分离出两个额外的对象(职责):

1)解析文件的内容以查找单个文档和 2)从文件系统中读取和写入文档

让我们先解决#1问题。使用一些“Extract Method”重构来本地化与内容解析相关的代码,然后进行“Extract Class”重构,将这些方法拉出到名为DocumentParser的类中。在你的例子中,这可能与“B”类似。如果您愿意,可以将与文档解析相关的测试从DocumentProcessorTest移动到新的DocumentParserTest,并模拟或存根DocumentParser中的DocumentProcessorTest

对于#2来说,它几乎是泡沫,冲洗,重复,你最终会得到像DocumentSerializer类,AKA“C”这样的东西。您也可以在DocumentProcessorTest中对此进行模拟,现在您没有文件I / O,并且测试驱动了一个具有两个额外协作者的组件,而无需设计整个类(使用单独的方法)。请注意,我们采用了“外部”方法,这实际上可以实现重构。

由于