2008-11-01 35 views
25

我听说使用TDD开发的项目更容易重构,因为这种做法产生了一套全面的单元测试,如果有任何更改破坏了代码,它们(希望)会失败。然而,我所看到的所有例子都涉及重构实现 - 例如,用更高效的算法改变算法。TDD如何使重构更容易?

我发现重构架构在设计仍在制定的早期阶段更为常见。接口改变,新的类被添加删除,甚至一个函数的行为可能会稍微改变(我认为我需要它来做到这一点,但它实际上需要这样做),等等......但是,如果每个测试案例是紧密耦合的对于这些不稳定的类,每次你改变设计时,你不需要不断地重写你的测试用例吗?

在什么情形下在TDD是好的改变和删除测试用例?你如何确定改变测试用例不会破坏它们?另外,似乎不得不同步全面的测试套件和不断变化的代码将是一个痛苦。我明白,一旦软件建立,稳定和正常运行,单元测试套件可以在维护过程中发挥巨大作用,但在TDD应该尽早提供帮助的时候,这在游戏的后期也是如此。

最后,一本关于TDD和/或重构的好书能够解决这些问题吗?如果是这样,你会推荐哪个?

+0

我一直在考虑同样的事情。从某种意义上说,测试可以说是违反了DRY,因为一段代码的行为既反映在代码中,也反映在测试它的代码中。 – Boris 2010-01-11 19:44:48

回答

8

加上它似乎不必 同步全面的测试套件 与不断变化的代码将是 痛苦。据我所知,单元 测试套件可以维护过程中极大的帮助 ,一旦软件 建成,稳定和运作,但 这在游戏中后期wheras TDD是 应该尽早和帮助。

我的确同意,在进行主要架构变更时,可以在这些早期变化中感受到有单元测试套件的开销,但我认为单元测试的好处远大于此退税。我认为这个问题常常是一个心理问题 - 我们倾向于认为我们的单元测试是代码基础的二等公民,我们不喜欢混淆它们。但随着时间的推移,我已经开始依赖于它们,欣赏它们的用处,我来把它们当作没有那么重要,不值得较少的维护和工作的代码库中的任何其他部分。

主要架构“变化”是否真正发生只有重构?如果你只是重构,然而显着,并且测试开始失败,那可能会告诉你,你在某处无意中更改了功能。这正是单元测试应该帮助你抓住的。如果你正在为功能性和结构的同时彻底改变,你可能要考虑放缓,进入红/绿/重构槽:没有新的(或改变)功能W/O额外的测试,并没有改变功能(并打破测试),同时重构。

更新(基于评论):

@Cybis已经提出了一个有趣反对我的主张重构不应该打破测试,因为重构不应该改变自己的行为。他的反对理由是,重构确实改变API,因此测试“破发”。

首先,我会鼓励任何人访问关于重构的规范参考:Martin Fowler's bliki。刚才我回顾它,有两件事情在我跳出:

  • Is changing an interface refactoring?马丁是指 重构为 “行为保留”的变化,当接口/ API改变 随后的所有调用这 意味着 接口/ API也必须改变。包括测试,我说。
  • 这并不意味着行为已更改。同样,福勒强调他的 重构的定义是 的变化是行为 保留

鉴于此,如果测试或测试必须在重构期间改变,我不认为这是“打破”测试。它只是重构的一部分,它保留了整个代码库的行为。我发现测试不得不改变,代码库中的任何其他部分不得不作为重构的一部分进行更改。 (这回到我之前说过的考虑测试成为代码库的一等公民)。

此外,我希望测试,即使是修改后的测试,一旦重构,就会继续通过已经完成了。无论测试什么样的测试(可能是该测试中的断言),在重构完成后应该仍然有效。否则,在重构期间,这是一种行为改变/倒退的红旗。

也许这种说法听起来毫无意义,但考虑一下:我们没有想到在生产代码库中移动代码块并期望它们能够继续在新的上下文中工作(新类,新方法签名,不管) 。我对测试有同样的感觉:重构可能会改变测试必须调用的API或测试必须使用的类,但最终测试的重点不应因为重构而改变。我可以想到的唯一例外是在重构过程中测试你可能想要改变的低级实现细节,比如用ArrayList或其他东西代替LinkedList,但是在这种情况下,认为测试是过度测试,并且太僵硬和脆弱。)

4

TDD说写失败先测试。测试是为了表明开发人员理解用例/故事/场景/过程应该实现的内容。

你再编写代码来满足测试。

如果需求改变或被误解,先编辑或改写测试。

红酒吧,绿吧,对不对?

福勒的重构对重构的参考,奇怪的是。

Scott Ambler的系列文章Dobb博士的('The Agile Edge ??')是TDD在实践中的一个很好的演练。

6

TDD的主要好处带给重构的是,开发商有更多的勇气去改变他们的代码。随着单元测试的准备就绪,开发人员不得不改变代码,然后运行它。如果xUnit栏仍然是绿色的,他们有信心继续前进。

就个人而言,我喜欢TDD,但并不鼓励过度TDD。也就是说,不要写太多的单元测试用例。单元测试应该足够了。如果你通过单元测试,那么当你想要进行架构变更时,你可能会发现你处于两难境地。生产代码的一个重大变化会带来很多单元测试用例的变化。所以,保持你的单元测试足够。

+1

然而,这个论点是部分TDD不是TDD。直接编写代码,无需进行测试,首先打破整个TDD口头禅。如果所有代码都是先写测试的,那么如何避免overddd? – Cybis 2008-11-02 02:12:33

1

Kent Beck的TDD的书。

先测试。遵循S.O.L.I.D OOP原则和使用好的重构工具是必不可少的,如果不需要的话。

+0

什么是S.O.L.I.D?请解释。 – Tilendor 2008-11-01 21:05:36

+0

SOLID是来自敏捷软件开发,原则,模式和实践的Robert Martins设计原则:单一责任原则,开放闭合原则,Liscov替代原则,界面分离原则和依赖倒置原则。 – quamrana 2008-11-02 19:47:31

1

在什么情况下在TDD中可以更改和删除测试用例吗?你如何确定改变测试用例不会破坏它们?另外,似乎不得不同步全面的测试套件和不断变化的代码将是一个痛苦。

测试和规格的要点是定义系统的正确行为。所以,很干脆:

if definition of correctness changes 
    change tests/specs 
end 

if definition of correctness does not change 
    # no need to change tests/specs 
    # though you still can for other reasons if you want/need 
end 

所以,如果应用/系统规范或期望的行为变化,这是一个必要改变测试。在这种情况下只更改代码而不是测试,显然是破坏了方法。你可能把它看作“痛苦”,但没有测试套件会更痛苦。 :)正如其他人所说的那样,拥有这种“敢于”改变代码的自由非常有助于解放。 :)

3

例如,用更高效的算法更改算法。

这不是重构,这是性能优化。重构大约是改进了现有代码的设计。也就是说,改变其形状以更好地满足开发人员的需求。改变代码意图影响外部可见行为不是重构,而是包括对效率的更改。

TDD的部分价值在于,您的测试可帮助您在改变生成结果的方式时保持可见行为不变。

+0

我知道。切换算法是一种优化,而不是重构问题。重构正在将代码移动行为重新安排到更合适的类中,将大类分解为更小的更具连贯性的类,等等......只是许多在线文章没有显示这样的实际例子。 – Cybis 2008-11-02 02:15:52

10

你需要牢记的一件事是TDD是而不是主要是测试策略,但是是一种设计策略。你先写测试,因为这有助于你提出更好的解耦设计。而更好的解耦设计也更容易重构。

当你改变一个类或方法的funcionality,这是自然的,测试必须改变了。事实上,遵循TDD意味着您当然会首先更改的测试。如果你不得不改变很多测试来改变一点功能,那通常意味着大多数测试都在过度指定行为 - 他们测试的东西比他们应该测试的要多。另一个问题可能是责任没有很好地包含在生产代码中。

不管是什么,当你遇到许多测试,因为一个小的变化的失败,你应该重构你的代码,以便它不会在将来再次发生。总是可以做到这一点,尽管并不总是很明显。

有了更大的设计变更,事情可能变得有点复杂。是的,有时写新的测试和丢弃旧测试会更容易。有时候,你至少可以编写一些集成测试来测试整个被重构的部分。你希望仍然有你的一套验收测试,这些测试大多不受影响。

我还没有读过它,但我听说过关于“XUnit测试模式 - 重构测试代码”这本书的好消息。

+0

在单个算法具有许多边界条件的情况下,您会做什么?用非平凡的BNF语法解析文本就是一个很好的例子。你有一半的解析器,但是需要稍微改变语法。因为你的语法树改变了,所有你的测试中断。该死的300 char lim – Cybis 2008-11-02 02:31:51