2008-08-10 130 views
8

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

回答

5

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

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

这有点破解,但我发现它非常有用。

0

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

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

3

@valters

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

如果测试没有破坏构建,那么您可以很容易地遇到这样的情况,即新代码会破坏构建,并且尽管测试已经涵盖了构建,但它暂时还不知道。失败的测试应该是测试或代码必须修复的红旗。

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

如果测试过于频繁地出现问题,可能表明测试的写入过于脆弱(取决于可能会改变的资源,例如数据库没有正确使用数据库单元,或者一个应该被嘲笑的外部Web服务),或者这可能表明团队中有开发人员没有给予测试适当的关注。

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

0

我已阅读使用遗留代码有效地工作,我同意这对处理“不可测试”代码非常有用。一些技术只适用于编译语言(我正在研究“旧”PHP应用程序),但我会说这本书的大部分内容适用于任何语言。

重构书籍有时会假定代码在重构之前处于半理想状态或“维护意识”状态,但我所使用的系统并不理想,并且被开发为“随时学习”应用程序或第一个应用程序对于一些使用的技术(我不会责怪最初的开发人员,因为我是其中之一),所以根本没有测试,代码有时是混乱的。这本书解决了这种情况,而其他重构书通常不会(在这个范围内)。我应该提到,我没有收到编辑和本书作者的任何款项;),但是我发现它非常有趣,因为在遗留代码领域缺乏资源(尤其是在我的语言中,法语,但这是另一回事)。