如何测试/更改未经测试和不可测试的代码?

时间:2008-08-10 18:49:54

标签: unit-testing refactoring legacy

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

4 个答案:

答案 0 :(得分:6)

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

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

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,但我最近在类似的情况下做了这个,因为我想测试一些与正在加载的数据完全无关的功能,所以它非常有效。我也发现这样的成员暴露在其他类似的情况下是有用的,我需要摆脱长时间在课堂上的一些依赖。

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

这有点像黑客,但我发现它非常有用。

答案 1 :(得分:3)

@valters

我不同意你的说法,即测试不应该破坏构建。测试应该表明应用程序没有为测试的功能引入新的错误(并且找到的错误表明缺少测试)。

如果测试没有破坏构建,那么你可以很容易地遇到新代码破坏构建并且暂时不知道的情况,即使测试覆盖了它。失败的测试应该是一个红色标记,要么必须修复测试或代码。

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

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

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

答案 2 :(得分:0)

我已阅读有效使用旧版代码,我同意它对处理“不可测试”代码非常有用。

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

重构书籍有时会假设代码在重构之前处于半理想状态或“维护感知”状态,但我处理的系统不太理想,并且被开发为“随时随地学习”应用程序,或者作为第一个应用程序开发使用了一些技术(我不会责怪初始开发人员,因为我就是其中之一),因此根本没有测试,代码有时会很混乱。这本书解决了这种情况,而其他重构书通常没有(好吧,不是这个程度)。

我应该提一下,我没有收到编辑的任何资金,也没有收到本书的作者;),但我发现它非常有趣,因为遗留代码领域缺乏资源(尤其是我的语言,法语,但这是另一个故事)。

答案 3 :(得分:0)

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

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