重构和测试驱动开发

时间:2009-03-18 10:09:47

标签: unit-testing refactoring testing

我目前正在阅读两本优秀的书籍“有效地使用遗留代码”和“清洁代码”。

他们让我思考我以全新的方式编写和使用代码的方式,但其中一个常见的主题是测试驱动的开发以及在进行测试之前通过测试扼杀所有内容并进行测试的想法改变或实施新的功能。

这导致了两个问题:

问题1: 如果我正在使用遗留代码。根据书籍,我应该进行测试,以确保我没有破坏任何东西。考虑一下我的方法有500行。我假设我将有一套等效的测试方法来测试该方法。当我拆分此函数时,是否为每个新的方法/类创建新的测试?

根据“清洁代码”,任何超过1/10秒的测试都需要花费太长时间。试图测试一个500长线遗留方法进入数据库并且上帝知道还有什么可能需要超过1/10秒。虽然我知道你需要打破依赖关系,但我遇到的问题是最初的测试创建。

问题2: 当代码被重新分解以至于在结构上它不再像原始代码(添加/移除到方法等的新参数)时会发生什么。那么测试还需要重新分解吗?在这种情况下,您可以在允许测试继续传递的同时改变系统的功能吗?在这种情况下,重新分解测试是否适合做?

虽然可以毫无疑问地进行假设,但我想知道在集体经验中是否有任何关于此类问题的想法/建议。

6 个答案:

答案 0 :(得分:4)

  1. 这是处理遗留代码时的交易。 Legacy意味着没有测试且紧密耦合的系统。为该代码添加测试时,您实际上是在添加集成测试。当您重构并添加避免网络调用等更具体的测试方法时,那些将是您的单元测试。你想保留两者,然后分开,这样你的大部分单元测试都会快速运行。
  2. 你这么做很小的步骤。实际上,您在测试和代码之间不断切换,而且您是正确的,如果您更改签名(小步骤),则需要更新相关测试。
  3. 同时在How can I improve my junit tests上查看我的“更新2”。它不是关于遗留代码和处理它已经具有的耦合,而是关于如何编写涉及外部系统的逻辑+测试,例如数据库,电子邮件等。

答案 1 :(得分:2)

0.1秒单位测试运行时间相当愚蠢。如果必须,单元测试不应该使用网络套接字,读取大文件或其他大量操作。是的,如果测试运行得很快就可以了,这样你就可以继续完成编写应用程序的主要工作,但最后得到最好的结果会更好,如果这意味着运行一个需要10秒的单元测试,那就是我的意思“我愿意。

如果您要重构,那么关键是花费尽可能多的时间来理解您正在重构的代码。这样做的一个好方法是为它编写一些单元测试。当您掌握某些代码块正在进行的操作时,您可以重构它,然后在您开始时为每个新方法编写测试是一种很好的做法。

答案 2 :(得分:1)

    • 是的,为新方法创建新测试。

    • 我认为1/10秒是你应该努力的目标。较慢的测试仍然比没有测试好得多。

  1. 尽量不要同时更改代码和测试。总是采取小步骤。

答案 3 :(得分:1)

如果你有一个冗长的传统方法来做X(可能因为它的大小而可能是Y和Z),真正的诀窍就是不要通过“修复”来破坏应用程序。遗留应用程序的测试有先决条件和后置条件,因此在分解之前你必须真正了解它们。测试有助于促进这一点。一旦你将这种方法分解为两种或更多种新方法,显然你需要知道每种方法的前/后状态,所以测试那些“保持诚实”,让你晚上睡得更好。

我不太担心第二个断言的十分之一。相反,我在编写单元测试时的目标是覆盖我的所有基础。显然,如果测试需要很长时间,那可能是因为测试的内容太多代码太多了。

最重要的是,你绝对不希望采取可能是一个工作系统的东西并“修复”它有时会起作用并在某些条件下失败。这就是测试可以提供帮助的地方。他们每个人都希望世界在测试开始时处于一种状态,最后处于新状态。只有你可以知道这两种状态是否正确。所有测试都可以“通过”,应用程序仍然可能出错。

任何时候代码都会发生变化,测试可能会发生变化,可能需要添加新的测试来解决对生产代码所做的更改。这些测试使用当前代码 - 如果需要更改参数无关紧要,仍然需要满足前/后条件。显然,将代码分解成更小的块是不够的。你的“分析师”必须能够理解你正在构建的系统 - 这是第一个工作。

根据您开始使用的“混乱”,使用遗留代码可能是一项真正的苦差事。我真的发现知道你所拥有的和它应该做什么(以及它是否在开始重构之前在第0步实际执行)是成功重构代码的关键。我认为,一个目标是,我应该能够抛弃旧的东西,将我的新代码放在适当的位置,让它按照广告(或更好)的方式工作。根据它所写的语言,原作者的假设以及将功能封装成可包含的块的能力,它可能是一个真正的技巧。

祝你好运!

答案 4 :(得分:0)

这是我的看法:

  1. 不,是的。首先要做的是进行单元测试,检查该500线方法的输出。而那么只有当你开始考虑拆分它时才会这样。理想情况下,流程将如下所示:

    • 为原有的传统500行巨头写一个测试
    • 弄清楚,首先用注释标记,你可以从该方法中提取哪些代码块
    • 为每个代码块编写一个测试。一切都会失败。
    • 逐个提取块。专注于让所有方法一次变为绿色。
    • 冲洗并重复,直至完成整件事

    经过这个漫长的过程,你会发现将某些方法转移到其他地方或者重复一些方法可能有意义,有些方法可以简化为单一的功能;这就是你知道你成功的方式。相应地编辑测试。

  2. 继续并重构,但是一旦您需要更改签名,请在对实际代码进行更改之前先对测试进行更改。这样你就可以确保在方法签名发生变化的情况下你仍在做出正确的断言。

答案 5 :(得分:0)

问题1:“当我拆分此功能时,是否为每个新的方法/类创建新的测试?”

一如既往,真正的答案是取决于。如果合适,将一些巨大的整体方法重构为较小的方法可以更简单,这些方法处理不同的组件部分,将新方法设置为私有/受保护,并保持现有的API不变,以便继续使用现有的单元测试。如果您需要测试新拆分的方法,有时将它们标记为包私有是有利的,这样您的单元测试类可以获取它们,但其他类不能。

问题2:“当代码被重新分解以至于在结构上它不再像原始代码那样时会发生什么?”

我的第一条建议是你需要一个好的IDE并且对正则表达式有很好的了解 - 尝试尽可能多地使用自动化工具进行重构。如果您小心谨慎,不引入新问题, 可以帮助您节省时间。正如你所说,你必须改变你的单元测试 - 但是如果你使用了好的OOP主体(你做得对吗?),那么它应该不会那么痛苦。

总的来说,重要的是要问自己关于重构的好处是否超过成本?我只是在摆弄架构和设计吗?我是在做一个重构,以便理解代码,是否真的需要它?我会咨询熟悉代码库的同事,了解他们对当前任务的成本/收益的看法。

还要记住,你在书中阅读的理论理想需要与现实世界的商业需求和时间表保持平衡。