TDD如何使重构更容易?

时间:2008-11-01 03:56:37

标签: refactoring tdd

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

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

在TDD的什么情况下可以更改和删除测试用例?你怎么能确定改变测试用例不会破坏它们?此外,似乎必须同步一个全面的测试套件和不断变化的代码将是一个痛苦。我知道单元测试套件在维护期间可以提供极大的帮助,一旦软件构建完成,稳定且运行正常,但是在TDD应该尽早提供帮助的情况下,游戏的后期也是如此。

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

8 个答案:

答案 0 :(得分:10)

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

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

无论是什么,当你因为一个小的改变而经历许多测试失败时,你应该重构你的代码,以便将来不再发生。总是可以这样做,但并不总是很明显如何。

随着更大的设计变化,事情会变得有点复杂。是的,有时候编写新测试并丢弃旧测试会更容易。有时,您至少可以编写一些集成测试来测试整个重构部分。而且你希望你的验收测试套件仍然没有受到影响。

我还没看过,但我听说过“XUnit测试模式 - 重构测试代码”一书的好消息。

答案 1 :(得分:8)

  

而且似乎不得不这样做   同步全面的测试套件   不断变化的代码将是   痛苦我明白这个单位   测试套件可以提供很大帮助   在维护期间,一旦软件   建立,稳定,运作,但   这是TDD比赛的后期   应该尽早帮助。

我确实认为,在进行重大架构更改时,可以在这些早期更改中感受到使用单元测试套件的开销,但我认为单元测试的好处远远超过这个缺点。我认为问题往往是一个心理问题 - 我们倾向于认为我们的单元测试是代码库的二等公民,我们不得不把它们搞得一团糟。但随着时间的推移,当我开始依赖它们并欣赏它们的用处时,我开始认为它们同样重要,并且不值得维护和工作,就像代码库的任何其他部分一样。

主要的建筑“变化”真的只发生了重构吗?如果你只是重构,无论如何显着,并且测试开始失败,那可能会告诉你,你无意中在某处改变了功能。这正是单元测试应该帮助你捕获的。如果您同时对功能和体系结构进行全面更改,您可能需要考虑放慢速度并进入红色/绿色/重构槽:没有新的(或更改的)功能,没有其他测试,也没有更改重构时的功能(和破坏测试)。

更新(根据评论):

@Cybis对我的主张提出了一个有趣的反对意见,即重构不应该破坏测试,因为重构不应该改变行为。他的反对意见是重构更改API,因此会测试“中断”。

首先,我鼓励任何人访问有关重构的规范参考:Martin Fowler's bliki。刚才我回顾了一下,有几件事情在我身上跳了出来:

  • Is changing an interface refactoring?马丁指的是 重构为 “保持行为”的变化 表示接口/ API更改时 然后所有的来电者 接口/ API也必须改变。 包括测试,我说。
  • 这并不意味着行为已经改变。再次,福勒强调他的 重构的定义就是这样 变化是行为 保留

鉴于此,如果在重构期间必须更改一个或多个测试,我不认为这会“破坏”测试。它只是重构的一部分,保留了整个代码库的行为。我发现必须更改的测试和代码库的任何其他部分必须作为重构的一部分进行更改之间没有区别。 (这可以追溯到我之前所说的关于将测试视为代码库的一等公民的问题。)

此外,一旦重构完成,我希望测试,甚至修改后的测试,继续传递。无论测试的是什么(可能是该测试中的断言)在重构完成后仍应有效。否则,这是一个红旗,表示在重构过程中某种行为发生了变化/退化。

也许这种说法听起来像是无稽之谈,但想一想:我们不会考虑在生产代码库中移动代码块并期望它们继续在新的上下文中工作(新类,新方法签名,等等)。我对测试也有同感:可能重构会更改测试必须调用的API,或者测试必须使用的类,但最后测试点不应因重构而改变。

(我能想到的唯一例外是测试可以在重构期间测试您可能想要更改的低级实现细节的测试,例如用ArrayList或其他东西替换LinkedList。但在这种情况下,可能会争论测试过度测试,太僵硬和脆弱。)

答案 2 :(得分:7)

TDD为重构带来的主要好处是开发人员有更多勇气改变他们的代码。准备好单元测试后,开发人员敢于更改代码,然后运行它。如果xUnit栏仍然是绿色,他们有信心继续。

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

答案 3 :(得分:4)

TDD表示先写一个失败的测试。编写测试是为了表明开发人员了解用例/故事/场景/过程应该达到的目的。

然后编写代码以满足测试。

如果要求发生变化或被误解,首先编辑或重写测试。

红条,绿条,对吧?

Fowler的重构 重构的参考,奇怪的是。

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

答案 4 :(得分:3)

  例如,

使用更高效的算法更改算法。

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

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

答案 5 :(得分:2)

我会推荐(和其他人一样):

答案 6 :(得分:1)

Kent Beck的TDD书。

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

答案 7 :(得分: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

因此,如果应用程序/系统规范或所需行为发生变化,则更改测试是必需。在这种情况下,仅更改代码而不是测试,显然是破坏了方法论。您可能会将其视为“痛苦”,但没有测试套件会更痛苦。 :)正如其他人所提到的那样,拥有“敢于”改变代码的自由是非常有能力和解放的。 :)