刚读完前四章 重构:改进现有规范的设计,我开始了第一次重构,几乎立即遇到了障碍。它源于要求在开始重构之前,应该对遗留代码进行单元测试。这使您可以确保您的重构没有改变原始代码所做的事情(仅限于此 怎么样 它做到了)。
所以我的第一个问题是:如何在遗留代码中对方法进行单元测试?如何在500行(如果我很幸运)的方法中进行单元测试,而不只执行一项任务?在我看来,我必须重构我的遗留代码只是为了使它可以单元测试。
有没有人有使用单元测试重构的经验?如果是这样,你有什么实际的例子可以和我分享吗?
我的第二个问题有点难以解释。这是一个例子:我想重构一个从数据库记录填充对象的遗留方法。我不是必须编写一个单元测试,将使用旧方法检索的对象与使用重构方法检索的对象进行比较吗?否则,我怎么知道我的重构方法产生与旧方法相同的结果?如果这是真的,那么我在源代码中保留旧的弃用方法多长时间?在测试几个不同的记录后,我是否只是打了它?或者,如果我在重构代码中遇到错误,是否需要保留一段时间?
最后,由于有几个人问过...遗留代码最初是用VB6编写的,然后移植到VB.NET,只需要进行最少的架构更改。
理论符合现实的好例子。单元测试旨在测试单个操作,许多模式纯粹主义者坚持 单一责任,所以我们有可爱的清洁代码和测试,以配合它。然而,在真实(凌乱)的世界中,代码(特别是遗留代码)做了很多事情并且没有测试。这需要的是重构清理混乱的剂量。
我的方法是使用单元测试工具构建测试,在单个测试中测试很多东西。在一次测试中,我可能正在检查数据库连接是否打开,更改大量数据,以及对数据库进行前/后检查。我不可避免地发现自己编写辅助类来进行检查,然后通常可以将这些帮助程序添加到代码库中,因为它们已经封装了紧急行为/逻辑/需求。我并不是说我有一个巨大的测试,我的意思是,mnay测试正在做的工作,纯粹主义者会称之为集成测试 - 这样的事情是否还存在?此外,我发现创建测试模板然后从中创建许多测试,检查边界条件,复杂处理等是有用的。
BTW我们在谈论哪种语言环境?有些语言比其他语言更适合重构。
有关如何重构遗留代码的说明,您可能需要阅读本书 有效地使用遗留代码。还有一个简短的PDF版本 这里。
根据我的经验,我不会为遗留代码中的特定方法编写测试,而是针对它提供的整体功能。这些可能会或可能不会与现有方法紧密相关。
在系统的任何级别编写测试(如果可以的话),如果这意味着运行数据库等,那么就这样吧。您将需要编写更多代码来断言代码当前正在执行的操作,因为500行+方法可能会包含很多行为。至于比较旧的和新的,如果你针对旧代码编写测试,它们会通过并覆盖它所做的一切,然后当你针对新代码运行它们时,你正在有效地检查新代码。
我这样做是为了测试一个复杂的sql触发器,我想重构,这是一个痛苦并花了时间,但一个月后,当我们在该区域发现另一个问题时,值得让那里的测试依赖。
根据我的经验,这是处理遗留代码时的现实。 Esko提到的书(使用Legacy ..)是一本出色的作品,描述了可以带你到那里的各种方法。
我已经看到了单元测试本身的类似问题,它已经发展成为系统/功能测试。为Legacy或现有代码开发测试最重要的是定义术语“单元”。它甚至可以是“从数据库读取”等功能单元。识别关键功能单元并维护增加价值的测试。
另外,Joel S.和Martin F.最近就TDD /单元测试进行了讨论。我的看法是定义单位并继续关注它是很重要的! URLS: 打开信封, 乔尔的成绩单 和 播客
这确实是尝试重新编写遗留代码的关键问题之一。您是否能够将问题域分解为更细粒度的内容?除了系统调用JDK / Win32 / .NET Framework JAR / DLL /程序集之外,500+行方法是否还有其他功能?即在500多行的庞然大物中是否有更细粒度的函数调用可以进行单元测试?
以下书: 单元测试的艺术 包含几个章节,其中包含有关如何在开发单元测试方面处理遗留代码的一些有趣想法。
我发现它非常有帮助。