在进行TDD时设计中大型应用程序?

时间:2009-12-31 00:36:47

标签: tdd

我很好地掌握了单元测试,DI,模拟以及所需的所有设计主要优点,以尽可能接近完整的代码覆盖范围(单一责任主体,想象'我将如何测试这个',因为我编码等等......)。

我最近的应用程序,我没有编写真正的TDD代码。在我编写代码时,我记住了单元测试,并在编写代码,重构等之后编写了我的测试。当它很容易做到时我做了TDD ...但是我没有像我现在这样做......这是我第一个充分利用DI,模拟框架等的项目,以及第一个完全代码覆盖的项目 - 随着我的进展,我从中学到了很多东西。我很想分配到我的下一个项目,所以我可以从头开始完全编写TDD代码。

我知道这是一个广泛的问题,我已经通过示例和XP Unleashed订购了TDD,但我希望简要介绍一下如何设计/编写一个大型应用程序进行TDD。

你是否编写了整个应用程序,只使用了存根代码?(例如,编写所有函数签名,接口,结构,并编写整个应用程序但不编写任何实际的实现)?我可以想象它适用于中小型,但这在大型应用程序中是否可行?

如果没有,您将如何编写系统中最高级别功能的第一次单元测试?让我们举例说 - 在一个Web服务上,你有一个名为DoSomethingComplicated(param1,...,param6)的函数暴露给世界。显然,首先为AddNumbers()之类的简单函数编写测试是微不足道的 - 但是当函数位于调用堆栈的顶部时,如此?

你还做前期设计吗?显然你仍然想做'架构'设计 - 例如,一个流程图显示IE与IIS交谈,它通过WCF与Windows服务进行通信,该服务与SQL数据库进行通信...一个显示所有SQL表及其字段的ERD,等......但是课堂设计呢?课程之间的相互作用等?你是在预先设计这个,还是继续编写存根代码,在你进行中重构交互,直到整个事物连接起来并且看起来它会起作用?

非常感谢任何建议

4 个答案:

答案 0 :(得分:20)

  • 你做前期设计吗?

你当然可以。你面前有一个很大的应用程序。在开始编写测试和代码之前,您必须了解它将具有的结构。您不必详细了解所有内容,但您应该对层,组件和接口有一些基本的了解。例如,如果您正在使用Web服务系统,那么您应该知道顶级服务是什么,并且他们的签名具有良好的第一近似值。

  • 你是否只使用存根代码编写整个应用程序?

没有。只有在测试中真的很难控制它们时,才能将它们存在。例如,我喜欢存根数据库和UI。我还将删除第三方接口。有时,如果它大大增加了测试时间,我会将我自己的一个组件存根,或者它迫使我创建太复杂的测试数据。但是大多数时候我让我的测试工作在一个非常好的集成系统上。

我不得不说我真的不喜欢严重依赖于模拟和存根的测试风格。不要误解我的意思,我认为模拟和存根对于解决难以测试的事物非常有用。但是我不喜欢编写难以测试的东西,因此我不会使用大量的模拟和存根。

  • 你如何为高级功能编写第一个单元测试?

大多数高级函数都具有退化行为。例如,登录是一个非常高级别的功能,可能非常复杂。但是,如果您尝试使用没有用户名和密码登录,系统的响应将非常简单。编写测试也很简单。所以你从堕落的案例开始。一旦你筋疲力尽,你就会进入下一个复杂程度。例如,如果用户尝试使用用户名但没有密码登录,该怎么办?一点一点地爬上复杂的阶梯,从不解决更复杂的方面,直到不那么复杂的方面都过去了。

这种策略的效果非常出色。你可能会认为你只是一直爬到边缘,从来没有吃过肉;但事实并非如此。相反,您会发现自己基于所有退化和异常情况设计代码的内部结构。当你最终到达主要流程时,你会发现你正在处理的代码结构有一个很好的孔,形状恰好可以插入主流。

  • 请不要先创建您的用户界面。

用户界面是误导性的东西。它们使您专注于系统的错误方面。相反,假设您的系统必须具有许多不同的UI。有些将是网络,有些将是厚客户端,有些将是纯文本。无论UI如何,都要将系统设计为正常工作。通过所有测试,首先获取所有业务规则。然后在以后插入UI。我知道,面对很多传统智慧,这种情况很快,但我不会这样做。

  • 请不要先设计数据库。

数据库是详细信息。保存详细信息以供日后使用相反,将系统设计为好像不知道您使用的是哪种数据库,将架构,表,行和列的任何概念保留在系统核心之外。实现业务规则,就好像所有数据始终保存在内存中一样。一旦您完成了所有业务规则,就可以在以后添加数据库。我再次知道,面对一些传统智慧,这种情况很快,但过早地将系统耦合到数据库是许多严重扭曲设计的来源。

答案 1 :(得分:12)

我是否只使用存根代码编写整个应用程序?

不,没有丝毫意义 - 这听起来像是一种非常浪费的方法。我们必须始终牢记,做TDD的根本原因是快速反馈。自动测试套件可以告诉我们是否比手动测试更快地破坏了任何东西。如果我们等到最后一刻连接在一起,我们就不会得到快速反馈 - 虽然我们可能会从单元测试中获得快速反馈,但我们不知道应用程序是否作为一个整体工作。单元测试只是我们验证应用程序所需执行的一种测试形式。

更好的方法是从最重要的功能开始,然后使用 out-in 方法从那里开始。这通常意味着从一些UI开始。

我这样做的方法是创建所需的UI。由于我们通常无法使用TDD开发UI,因此我只需使用所选技术创建View。那里没有测试,但是我将UI连接到一些API(最好使用声明性数据绑定),这就是测试开始的时候。

一开始,我会TDD我的ViewModel / Presentation Models和相应的控制器,可能会对一些响应进行硬编码以查看UI的工作原理。一旦我运行它时没有爆炸的东西,我会检查代码(记住,许多小的增量签到)。

我随后以垂直向下工作,并确保此特定UI可以一直到数据源(或其他),忽略所有其他功能。

功能完成后,我可以开始下一个功能。我描绘这个过程的方式是我通过一次做一个垂直切片来填写应用程序,直到完成所有功能。

以这种方式启动绿地应用程序总是需要额外的长时间来完成第一个功能,因为这是你必须连接所有内容的地方,所以选择一些简单的东西(比如应用程序的初始视图) )尽量保持简单。第一个功能完成后,下一个功能变得更加容易,因为基础已经到位。

我还在预先设计吗?

不多,不。在开始之前,我通常会考虑整体设计,当我在团队中工作时,我们会在开始之前在白板或幻灯片上绘制整体架构。

这或多或少局限于

  • 图层的数量和名称(UI,演示逻辑,域模型,数据访问等)。
  • 使用的技术(WPF,ASP.NET MVC,SQL Server,.NET 3.5或诸如此类)
  • 我们如何构建生产代码和测试代码,以及我们使用哪些测试技术
  • 代码的质量要求(结对编程,静态代码分析,编码标准等)

其余的我们一目了然,但随着我们的进展,我们在白板上使用了许多临时设计会议。

答案 2 :(得分:1)

+1好问题

我真的不知道答案,但我会从构建我可以测试的类块开始,然后构建到应用程序中,而不是使用顶级的东西。是的,我会有一个粗略的前端设计接口,否则我认为你会发现这些接口经常改变,因为你重构它将是一个真正的阻碍。

TDD By Example无助于我不思考。 IIRC它通过一个简单的例子。我正在阅读Roy Osherove的单元测试艺术,虽然它似乎全面涵盖了模拟和存根等工具和技术,但到目前为止的例子似乎也非常简单,我没有看到它告诉你如何处理大型项目。

答案 3 :(得分:1)

  • 您是否只使用存根代码编写整个应用程序?

为了测试我们的系统,我们主要进行单元,集成和远程服务测试。在单元测试中,我们存在所有长时间运行,耗时且外部的服务,即数据库操作,Web服务连接或与外部服务的任何连接。这是为了确保我们的测试快速,独立,并且不依赖于任何外部服务的响应来为我们提供快速反馈。我们已经学到了很多困难,因为我们确实有一些测试可以进行数据库操作,这使得它非常慢,违背了“单元测试必须快速运行”的原则

在集成测试中,我们测试数据库操作但仍然不测试Web服务和外部服务,因为这可能会使测试变得脆弱,具体取决于它们的可用性,我们使用自动测试在我们编码的同时在后台运行测试。

但是,测试任何类型的远程服务,我们有连接到外部服务的测试,对它们执行操作并获得响应。对测试而言,重要的是他们的反应和他们的最终状态,如果它对测试很重要的话。这里重要的是,我们将这些测试保存在另一个名为remote的目录中(这是我们创建并遵循的约定),当我们将任何代码合并到master /时,这些远程测试仅由我们的CI(持续集成)服务器运行trunk branch并将其推送/提交到repo,这样我们就可以快速了解那些可能影响我们应用程序的外部服务是否有任何变化。

  • 我还在预先设计吗?

是的,但我们基本上没有做大型设计,基本上是鲍勃叔叔(Robert C. Martin)所说的。 此外,在将自己融入编码并创建一些类协作图之前,我们先进入白板,以便明确并确保团队中的每个人都在同一页面上,这也有助于我们在团队成员之间划分工作。