如何使用TDD获得正确的初始API?


12

这可能是一个很愚蠢的问题,因为我是第一次尝试TDD。我喜欢它带来的信心以及代码的总体上更好的结构,但是当我开始将其应用于比一类玩具示例更大的东西时,我遇到了困难。

假设您正在编写各种各样的库。您知道它必须要做的事情,您知道应该如何实现它的一般方法(从体系结构角度来说),但是您不断“发现”需要在编写代码时对公共API进行更改。也许您需要将此私有方法转换为策略模式(现在需要在测试中通过模拟的策略),也许您错失了职责,并拆分了一个现有的类。

当您在现有代码上进行改进时,TDD似乎非常合适,但是当您从头开始编写所有内容时,除非您进行了较大的设计,否则编写测试的API有点“模糊”。当您已经对该签名进行了30次测试的方法(对于该部分而言,其行为)更改时,该怎么办?一旦合并起来,有很多测试需要更改。


3
一种方法进行30次测试?听起来这种方法过于复杂,或者您编写的测试太多。
Minthos

好吧,我可能有些夸张地表达了我的观点。在检查了代码之后,我通常每次测试少于10种方法,其中大多数方法都在5种以下。但是整个“返回并手动更改它们”部分非常令人沮丧。
Vytautas Mackonis

6
@Minthos:我可以想到6个测试,任何采用字符串的方法在首次草稿写入时通常会失败或性能不佳(空,空,太长,未正确定位,性能缩放差) 。对于采用集合的方法类似。对于非平凡的方法,30听起来很大,但也不太现实。
史蒂文·埃弗斯

Answers:


13

您所谓的“前端大设计”,我称为“您的类架构的明智规划”。

您不能通过单元测试来扩展体系结构。 甚至鲍伯叔叔也这么说。

如果您不考虑架构,而您正在做的是忽略架构并将测试放在一起并通过测试,那么您就是在破坏可以使建筑物保持正常运转的事物,因为这是集中在系统的结构和可靠的设计决策,这些决策有助于系统保持其结构完整性。

http://s3.amazonaws.com/hanselminutes/hanselminutes_0171.pdf,第4页

验证您的结构设计的角度来看,我认为采用TDD更为明智。如果不进行测试,您如何知道设计不正确?以及如何在不更改原始测试的情况下确认更改正确无误?

软件之所以“软”,恰恰是因为它可能会发生变化。如果您对更改的数量感到不满意,请继续获得体系结构设计的经验,并且随着时间的推移,您需要对应用程序体系结构进行的更改数量将减少。


问题是,即使有了“明智的计划”,您也希望它会改变。通常,我会保留大约80%的初始体系结构,并在此之间进行一些更改。那20%的事困扰着我。
Vytautas Mackonis

2
我认为这只是软件开发的本质。您不能指望在第一次尝试时就能获得正确的整个体系结构。
罗伯特·哈维

2
+1,这与TDD并不相反。TDD在您开始编写代码时(确切地说是在设计结束时)开始。TDD可以帮助您了解设计中缺少的内容,从而使您可以重构设计和实现,然后继续。
史蒂文·埃弗斯

2
实际上,根据鲍勃(我完全同意他的说法),编写代码也是设计。拥有高层架构绝对是必要的,但是设计在您编写代码时不会结束。
迈克尔·布朗

真是个好答案,真是妙不可言。我看到这么多人,无论是支持还是反对TDD的人,当实际上是对古老的疯狂瀑布设计阶段的一次打击时,他们似乎都将“没有大的设计放在前面”作为“根本没有设计,只是编写代码”。设计始终是一项不错的时间投入,并且对于任何不重要的项目的成功至关重要。
2016年

3

如果您执行TDD。未经测试驱动,您无法更改签名和行为。因此,失败的30个测试要么在流程中删除,要么与代码一起更改/重构。或者它们现在已过时,可以安全删除。

您不能忽略红绿重构周期中30倍的红色吗?

您的测试应与生产代码一起重构。如果可以,请在每次更改后重新运行所有测试。

不要害怕删除TDD测试。一些测试最终会测试构建基块,以达到理想的结果。在功能级别上的期望结果至关重要。如果只有一种方法可以达到结果,或者您最初陷入死胡同,那么选择/发明的算法中的中间步骤进行的测试可能没有价值,也可能没有价值。

有时,您可以创建一些不错的集成测试,保留这些测试,然后删除其余的测试。这在某种程度上取决于您是由内而外还是自上而下以及您执行了多大的步骤。


1

正如罗伯特·哈维(Robert Harvey)所说,您可能正在尝试将TDD用于应由其他概念工具(即“设计”或“建模”)处理的事物。

尝试以非常抽象的方式(“通用”,“模糊”)设计(或“建模”)您的系统。例如,如果您必须对汽车进行建模,则只需为汽车类添加一些模糊的方法和字段,例如startEngine()和int座位。也就是说:描述您想向公众公开的内容,而不是您想要如何实现的。尝试仅公开基本功能(读取,写入,启动,停止等),并保留详细的客户端代码(prepareMyScene(),killTheEnemy()等)。

使用这个简单的公共界面写下您的测试。

在需要时更改类和方法的内部行为。

如果并且当您需要更改公共界面和测试套件时,请停下来思考。这很可能表明您的API和设计/建模中存在错误。

更改API并不罕见。大多数版本为1.0的系统都明确警告程序员/用户不要对其API进行更改。尽管如此,持续不断的,不受控制的API更改流程显然是设计不良(或完全缺失)的标志。

顺便说一句:您通常应该只对每种方法进行少量测试。根据定义,一种方法应该对某种数据实施明确定义的“操作”。在理想情况下,这应该是与单个测试相对应的单个动作。在现实世界中,很少有相同动作的不同“版本”,并且很少有不同的相应测试,这是不寻常的(而且不是错误的)。当然,您应该避免对同一方法进行30次测试。这清楚地表明该方法尝试执行过多操作(并且其内部代码变得不受控制)。


0

我从用户的角度来看它。例如,如果您的API让我创建具有名称和年龄的Person对象,则最好有一个Person(字符串名称,int age)构造函数和用于名称和年龄的访问器方法。为有或没有姓名和年龄的新人员创建测试用例很简单。

道格

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.