使用单元测试进行实用重构
刚刚阅读了重构:改进现有代码的设计的前四章,我开始了第一次重构,但几乎立刻就遇到了障碍。 它源于这样的要求:在开始重构之前,应该对遗留代码进行单元测试。 这可以让您确保您的重构不会改变原始代码的功能(只是如何改变它)。
所以我的第一个问题是:如何对遗留代码中的方法进行单元测试? 如何对 500 行(如果我幸运的话)方法进行单元测试,而该方法不仅仅执行一项任务? 在我看来,我必须重构我的遗留代码才能使其可进行单元测试。
有人有使用单元测试重构的经验吗? 如果是这样,您有什么实际例子可以与我分享吗?
我的第二个问题有点难以解释。 这是一个示例:我想重构一个从数据库记录填充对象的遗留方法。 我是否必须编写一个单元测试来比较使用旧方法检索的对象与使用重构方法检索的对象? 否则,我怎么知道我的重构方法会产生与旧方法相同的结果? 如果这是真的,那么我将在源代码中保留旧的已弃用方法多长时间? 我是不是在测试了几条不同的记录后才敲击它? 或者,我是否需要将其保留一段时间,以防在重构的代码中遇到错误?
最后,由于有几个人问...遗留代码最初是用 VB6 编写的,然后以最小的架构更改移植到 VB.NET。
Having just read the first four chapters of Refactoring: Improving the Design of Existing Code, I embarked on my first refactoring and almost immediately came to a roadblock. It stems from the requirement that before you begin refactoring, you should put unit tests around the legacy code. That allows you to be sure your refactoring didn't change what the original code did (only how it did it).
So my first question is this: how do I unit-test a method in legacy code? How can I put a unit test around a 500 line (if I'm lucky) method that doesn't do just one task? It seems to me that I would have to refactor my legacy code just to make it unit-testable.
Does anyone have any experience refactoring using unit tests? And, if so, do you have any practical examples you can share with me?
My second question is somewhat hard to explain. Here's an example: I want to refactor a legacy method that populates an object from a database record. Wouldn't I have to write a unit test that compares an object retrieved using the old method, with an object retrieved using my refactored method? Otherwise, how would I know that my refactored method produces the same results as the old method? If that is true, then how long do I leave the old deprecated method in the source code? Do I just whack it after I test a few different records? Or, do I need to keep it around for a while in case I encounter a bug in my refactored code?
Lastly, since a couple people have asked...the legacy code was originally written in VB6 and then ported to VB.NET with minimal architecture changes.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
有关如何重构遗留代码的说明,您可能需要阅读《有效处理遗留代码》一书。 此处还提供了简短的 PDF 版本。
For instructions on how to refactor legacy code, you might want to read the book Working Effectively with Legacy Code. There's also a short PDF version available here.
理论与现实结合的好例子。 单元测试旨在测试单个操作,许多模式纯粹主义者坚持单一职责,所以我们有可爱干净的代码和与之相配的测试。 然而,在现实(混乱)的世界中,代码(尤其是遗留代码)做了很多事情并且没有测试。 这需要大量的重构来清理混乱。
我的方法是使用单元测试工具构建测试,在单个测试中测试很多东西。 在一项测试中,我可能会检查数据库连接是否打开,更改大量数据,并对数据库进行前后检查。 我不可避免地发现自己编写帮助程序类来进行检查,并且通常可以将这些帮助程序添加到代码库中,因为它们封装了紧急行为/逻辑/需求。 我并不是说我有一个巨大的测试,我的意思是许多测试正在做纯粹主义者称之为集成测试的工作 - 这样的事情还存在吗? 我还发现创建一个测试模板然后从中创建许多测试来检查边界条件、复杂处理等很有用。
顺便说一句,我们谈论的是哪种语言环境? 有些语言比其他语言更适合重构。
Good example of theory meeting reality. Unit tests are meant to test a single operation and many pattern purists insist on Single Responsibilty, so we have lovely clean code and tests to go with it. However, in the real (messy) world, code (especially legacy code) does lots of things and has no tests. What this needs is dose of refactoring to clean the mess.
My approach is to build tests, using the Unit Test tools, that test lots of things in a single test. In one test, I may be checking the DB connection is open, changing lots of data, and doing a before/after check on the DB. I inevitably find myself writing helper classes to do the checking, and more often than not those helpers can then be added into the code base, as they have encapsulated emergent behaviour/logic/requirements. I don't mean I have a single huge test, what I do mean is mnay tests are doing work which a purist would call an integration test - does such a thing still exist? Also I've found it useful to create a test template and then create many tests from that, to check boundary conditions, complex processing etc.
BTW which language environment are we talking about? Some languages lend themselves to refactoring better than others.
根据我的经验,我编写的测试不是针对遗留代码中的特定方法,而是针对它提供的整体功能。 这些可能会也可能不会与现有方法紧密对应。
From my experience, I'd write tests not for particular methods in the legacy code, but for the overall functionality it provides. These might or might not map closely to existing methods.
在系统的任何级别(如果可以的话)编写测试,如果这意味着运行数据库等,那就这样吧。 您将需要编写更多代码来断言代码当前正在执行的操作,因为 500 行以上的方法可能会包含很多行为。 至于比较新旧代码,如果您针对旧代码编写测试,它们会通过并且涵盖它所做的一切,然后当您针对新代码运行它们时,您将有效地检查旧代码与新代码。
我这样做是为了测试一个我想要重构的复杂的 sql 触发器,这很痛苦并且需要时间,但一个月后,当我们在该领域发现另一个问题时,值得在那里进行测试来依赖。
Write tests at what ever level of the system you can (if you can), if that means running a database etc then so be it. You will need to write a lot more code to assert what the code is currently doing as a 500 line+ method is going to possibly have a lot of behaviour wrapped up in it. As for comparing the old versus the new, if you write the tests against the old code, they pass and they cover everything it does then when you run them against the new code you are effectively checking the old against the new.
I did this to test a complex sql trigger I wanted to refactor, it was a pain and took time but a month later when we found another issue in that area it was worth having the tests there to rely on.
根据我的经验,这是处理遗留代码时的现实。 Esko 提到的书(Working with Legacy..)是一本出色的作品,它描述了可以带您到达那里的各种方法。
我在单元测试本身中也看到过类似的问题,单元测试本身已经发展成为系统/功能测试。 为遗留代码或现有代码开发测试最重要的是定义术语“单元”。 它甚至可以是“从数据库读取”等功能单元。识别关键功能单元并维护可增加价值的测试。
顺便说一句,Joel S. 和 Martin F. 最近就 TDD/单元测试进行了讨论。 我的看法是,定义单位并持续关注它很重要! 网址: 公开信、Joel 的文字记录和播客
In my experience this is the reality when working on Legacy code. Book (Working with Legacy..) mentioned by Esko is an excellent work which describes various approaches which can take you there.
I have seen similar issues with out unit-test itself which has grown to become system/functional test. Most important thing to develop tests for Legacy or existing code is to define the term "unit". It can be even functional unit like "reading from database" etc. Identify key functional units and maintain tests which adds value.
As an aside, there was recent talk between Joel S. and Martin F. on TDD/unit-tests. My take is that it is important to define unit and keep focus on it! URLS: Open Letter, Joel's transcript and podcast
这确实是尝试修改遗留代码的关键问题之一。 您能否将问题域分解为更细粒度的内容? 除了对 JDK/Win32/.NET Framework JAR/DLL/程序集的系统调用之外,该 500 多行方法是否还会进行其他操作? 即,在这个 500 多行的庞然大物中是否有更细粒度的函数调用可供您进行单元测试?
That really is one of the key problems of trying to refit legacy code. Are you able to break the problem domain down to something more granular? Does that 500+ line method make anything other than system calls to JDK/Win32/.NET Framework JARs/DLLs/assemblies? I.e. Are there more granular function calls within that 500+ line behemoth that you could unit test?
以下书籍:单元测试的艺术包含几个章节,其中包含一些关于如何进行有趣的想法在开发单元测试方面处理遗留代码。
我发现它很有帮助。
The following book: The Art of Unit Testing contains a couple of chapters with some interesting ideas on how to deal with legacy code in terms of developing Unit Tests.
I found it quite helpful.