【问题标题】:How do you test/change untested and untestable code?您如何测试/更改未经测试和不可测试的代码?
【发布时间】:2008-08-10 18:49:53
【问题描述】:

最近我不得不在旧系统上更改一些代码,其中并非所有代码都有单元测试。
在进行更改之前,我想编写测试,但是每个类都创建了很多依赖项和其他反模式,这使得测试非常困难。
显然,我想重构代码以使其更易于测试,编写测试然后更改它。
你会这样做吗?或者您会花费大量时间编写难以编写的测试,这些测试将在重构完成后大部分被删除?

【问题讨论】:

    标签: unit-testing refactoring legacy


    【解决方案1】:

    首先,here's a great article with tips on unit testing。其次,我发现避免对旧代码进行大量更改的好方法是稍微重构它,直到您可以对其进行测试。一种简单的方法是保护私有成员,然后覆盖受保护的字段。

    例如,假设您有一个在构造函数期间从数据库加载一些内容的类。在这种情况下,您不能只覆盖受保护的方法,而是可以将数据库逻辑提取到受保护的字段,然后在测试中覆盖它。

    public class MyClass {
        public MyClass() {
            // undesirable DB logic
        }
    }
    

    变成

    public class MyClass {
        public MyClass() {
            loadFromDB();
        }
    
        protected void loadFromDB() {
            // undesirable DB logic
        }
    }
    

    然后你的测试看起来像这样:

    public class MyClassTest {
        public void testSomething() {
            MyClass myClass = new MyClassWrapper();
            // test it
        }
    
        private static class MyClassWrapper extends MyClass {
            @Override
            protected void loadFromDB() {
                // some mock logic
            }
        }
    }
    

    这是一个不好的例子,因为你可以在这种情况下使用 DBUnit,但实际上我最近在类似的情况下这样做了,因为我想测试一些与正在加载的数据完全无关的功能,所以它非常有效.我还发现这样的成员暴露在其他类似的情况下很有用,在这些情况下我需要摆脱在一个类中已经存在很长时间的一些依赖关系。

    如果您正在编写框架,我建议您不要使用此解决方案,除非您真的不介意将成员公开给框架的用户。

    这有点小技巧,但我发现它非常有用。

    【讨论】:

      【解决方案2】:

      @valters

      我不同意您关于测试不应破坏构建的说法。测试应该表明应用程序没有为所测试的功能引入新的错误(并且发现的错误表明缺少测试)。

      如果测试没有破坏构建,那么您很容易遇到新代码破坏构建并且暂时不为人所知的情况,即使测试覆盖了它。失败的测试应该是一个危险信号,表明必须修复测试或代码。

      此外,允许测试不破坏构建将导致失败率缓慢上升,以至于您不再拥有一组可靠的回归测试。

      如果出现测试中断过于频繁的问题,则可能表明测试的编写方式过于脆弱(依赖于可能发生变化的资源,例如没有正确使用 DB Unit 的数据库,或者应该模拟的外部 Web 服务),或者这可能表明团队中有开发人员没有给予测试适当的关注。

      我坚信应该尽快修复失败的测试,就像修复无法尽快编译的代码一样。

      【讨论】:

        【解决方案3】:

        我不知道你为什么会说一旦重构完成,单元测试就会被删除。实际上你的单元测试套件应该在主构建之后运行(你可以创建一个单独的“测试”构建,它只是在构建主产品之后运行单元测试)。然后,您将立即查看一件的更改是否会破坏其他子系统中的测试。请注意,这与在构建期间运行测试有点不同(正如一些人可能提倡的那样) - 一些有限的测试在构建期间很有用,但通常仅仅因为某些单元测试失败而“崩溃”构建是没有生产力的。

        如果您正在编写 Java(很有可能),请查看 http://www.easymock.org/ - 可能有助于减少测试中的耦合。

        【讨论】:

          【解决方案4】:

          我已阅读《有效地使用遗留代码》,我同意它对于处理“不可测试”的代码非常有用。

          有些技术只适用于编译语言(我正在研究“旧”PHP 应用程序),但我想说本书的大部分内容适用于任何语言。

          重构书籍有时会假设代码在重构之前处于半理想状态或“维护意识”状态,但我工作的系统并不理想,并且被开发为“边走边学”应用程序,或者作为第一个应用程序使用了一些技术(我不怪最初的开发人员,因为我是其中之一),所以根本没有测试,代码有时很混乱。本书解决了这种情况,而其他重构书籍通常不会(嗯,没有到这种程度)。

          我应该提一下,我没有从这本书的编辑或作者那里收到任何钱;),但我觉得这很有趣,因为遗留代码领域缺乏资源(尤其是我的语言,法语,但那是另一回事了)。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2019-01-18
            • 2018-02-24
            • 2013-03-14
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-07-06
            相关资源
            最近更新 更多