我们如何测试/更改未经测试和无法测试的代码?

时间:2020-03-05 18:38:52  来源:igfitidea点击:

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

解决方案

回答

首先,这是一篇很棒的文章,其中包含有关单元测试的提示。其次,我发现一种避免对旧代码进行大量更改的好方法是将其重构一点,直到可以对其进行测试为止。一种简单的方法是使私有成员受保护,然后覆盖受保护的字段。

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

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,但实际上我最近在类似的情况下也这样做,因为我想测试一些与加载的数据完全无关的功能,因此非常有效。我还发现,这样的成员公开在其他类似的情况下很有用,在这些情况下,我需要摆脱类中已经存在很长时间的某些依赖项。

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

这有点骇人听闻,但我发现它非常有用。

回答

我不确定为什么我们说重构一旦完成就将删除单元测试。实际上,单元测试套件应在主构建之后运行(我们可以创建一个单独的"测试"构建,该构建仅在主产品构建之后运行单元测试)。然后,我们将立即看到一件产品中的更改是否破坏了其他子系统中的测试。请注意,这与在构建期间运行测试有些不同(有些人可能会主张),在构建期间有用的有限测试是有用的,但是通常由于某些单元测试碰巧失败而"崩溃"构建无效。

如果我们正在编写Java(有可能),请访问http://www.easymock.org/,这可能有助于减少测试的耦合。

回答

@valters

我不同意说法,即测试不应破坏构建。测试应该表明应用程序没有引入针对测试功能的新错误(发现的错误表明缺少测试)。

如果测试没有破坏构建,那么我们很容易遇到新代码破坏构建并且即使有测试覆盖了它的情况也很久未见的情况。测试失败应为必须修正测试或者代码的危险信号。

此外,允许测试不中断构建会导致故障率缓慢上升,以至于我们不再拥有可靠的一组回归测试。

如果测试频繁中断存在问题,则可能表明测试以太脆弱的方式编写(取决于可能更改的资源,例如未正确使用DB Unit的数据库或者外部Web服务(应该被嘲笑),否则可能表明团队中有一些开发人员没有对测试给予应有的重视。

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

回答

我已经阅读了有效地使用遗留代码,并且我同意它对于处理"令人困惑的"代码非常有用。

有些技术仅适用于编译语言(我正在使用"旧" PHP应用程序),但是我想说本书的大部分内容都适用于任何语言。

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

我应该提及的是,我没有从本书的编辑和作者那里得到任何款项;),但是我发现它非常有趣,因为在遗留代码领域(尤其是在我的语言法语中)缺乏资源,但是那是另一个故事)。