我听说使用TDD开发的项目更容易重构,因为这种做法产生了一套全面的单元测试,如果任何更改破坏了代码,它将(希望)失败.然而,我所看到的所有这些例子都涉及重构实现 - 例如,用更高效的算法改变算法.
我发现重构架构在设计仍在制定的早期阶段更为常见.接口改变,添加和删除新类,甚至函数的行为可能会稍微改变(我认为我需要它来做这个,但它实际上需要这样做)等等......但是如果每个测试用例都紧密耦合对于这些不稳定的类,您每次更改设计时都不必不断地重写测试用例吗?
在TDD的什么情况下可以改变和删除测试用例?你怎么能确定改变测试用例不会破坏它们?此外,似乎必须同步一个全面的测试套件和不断变化的代码将是一个痛苦.我知道单元测试套件在维护期间可以提供很大的帮助,一旦软件构建完成,稳定并且运行正常,但是在游戏的后期TDD应该尽早提供帮助.
最后,关于TDD和/或重构的好书会解决这些问题吗?如果是这样,你会推荐哪一个?
你需要记住的一件事是,TDD是不是主要是测试策略,而是一种设计策略.您首先编写测试,因为这有助于您提出更好的解耦设计.更好的解耦设计也更容易重构.
当你改变一个类或方法的功能时,测试也必须改变.实际上,遵循TDD意味着您首先要更改测试.如果你必须改变很多测试只是改变一点功能,这通常意味着大多数测试都过度指定了行为 - 他们测试的测试比他们应该测试的更多.另一个问题可能是责任没有很好地封装在您的生产代码中.
无论是什么,当你因为一个小的改变而经历许多测试失败时,你应该重构你的代码,以便将来不再发生.它总是可以做到这一点,但并不总是显而易见的.
随着更大的设计变化,事情会变得有点复杂.是的,有时候编写新测试并丢弃旧测试会更容易.有时,您至少可以编写一些集成测试来测试整个重构部分.并且您希望仍然拥有您的验收测试套件,这些测试大多不受影响.
我还没看过,但是我听说过"XUnit测试模式 - 重构测试代码"这本书.
此外,似乎必须同步一个全面的测试套件和不断变化的代码将是一个痛苦.我知道单元测试套件在维护期间可以提供很大的帮助,一旦软件构建完成,稳定并且运行正常,但是在游戏的后期TDD应该尽早提供帮助.
我确实认为,当主要的架构发生变化时,可以在这些早期的变化中感受到单元测试套件的开销,但我认为单元测试的好处远大于这个缺点.我认为问题往往是一个心理问题 - 我们倾向于认为我们的单元测试是代码库的二等公民,我们不得不把它们搞得一团糟.但是随着时间的推移,当我开始依赖它们并欣赏它们的用处时,我开始认为它们与代码库的任何其他部分一样重要且不值得维护和工作.
主要的建筑"变化"真的只发生了重构吗?如果你只是重构,无论如何显着,并且测试开始失败,那可能会告诉你,你无意中在某处改变了功能.这正是单元测试应该帮助你捕获的.如果您同时对功能和体系结构进行全面更改,您可能需要考虑放慢速度并进入红色/绿色/重构槽:没有新的(或更改的)功能,没有其他测试,也没有更改重构时的功能(和破坏测试).
更新(根据评论):
@Cybis对我的主张提出了一个有趣的反对意见,即重构不应该破坏测试,因为重构不应该改变行为.他的反对意见是重构确实改变了API,因此测试"中断".
首先,我鼓励任何人访问关于重构的规范参考:Martin Fowler的bliki.刚才我回顾了一下,有几件事情在我身上跳了出来:
是在改变界面重构吗? Martin将重构称为"行为保留"更改,这意味着当接口/ API发生更改时,该接口/ API的所有调用者也必须更改.我说包括测试在内.
这并不意味着行为发生了变化.同样,福勒强调他对重构的定义是变化是保持行为 .
鉴于此,如果在重构期间必须更改一个或多个测试,我不认为这会"破坏"测试.它只是重构的一部分,保留了整个代码库的行为.我发现必须更改的测试和代码库的任何其他部分必须作为重构的一部分进行更改之间没有区别.(这可以追溯到我之前所说的关于将测试视为代码库的一等公民.)
另外,我希望一旦重构完成,测试,甚至是修改过的测试,都会继续通过.无论测试的是什么(可能是测试中的断言)在重构完成后仍应有效.否则,这是一个在重构过程中以某种方式改变/退化的行为的红旗.
也许这个说法听起来像是无稽之谈,但想一想:我们不会考虑在生产代码库中移动代码块并期望它们继续在新的上下文中工作(新类,新方法签名,等等).我对测试也有同样的看法:也许重构会改变测试必须调用的API,或者测试必须使用的类,但最后测试点不应该因为重构而改变.
(我能想到的唯一例外是测试可以在重构期间测试您可能想要更改的低级实现细节的测试,例如用ArrayList或其他东西替换LinkedList.但在这种情况下,可以认为测试过度测试,太僵硬和脆弱.)
TDD为重构带来的主要好处是开发人员有更大的勇气来改变他们的代码.准备好单元测试后,开发人员敢于更改代码,然后运行它.如果xUnit栏仍然是绿色,他们有信心继续前进.
就个人而言,我喜欢TDD,但不鼓励超过TDD.也就是说,不要写太多的单元测试用例.单元测试应该足够了.如果您进行单元测试,那么当您想要进行架构更改时,您可能会发现自己处于两难境地.生产代码的一个重大变化将带来许多单元测试用例的变化.所以,只需保持你的单元测试.