最近我不得不在旧系统上更改一些代码,其中并非所有代码都有单元测试。
在进行更改之前,我想编写测试,但是每个类都创建了很多依赖项和其他反模式,这使得测试变得非常困难。
显然,我想重构代码以使其更易于测试、编写测试然后更改它。
你会这样做吗?或者您是否会花费大量时间编写难以编写的测试,这些测试将在重构完成后大部分被删除?
4 回答
首先,这是一篇很棒的文章,其中包含有关单元测试的提示。其次,我发现避免对旧代码进行大量更改的好方法是稍微重构它,直到您可以对其进行测试。一种简单的方法是保护私有成员,然后覆盖受保护的字段。
例如,假设您有一个在构造函数期间从数据库加载一些内容的类。在这种情况下,您不能只覆盖受保护的方法,而是可以将数据库逻辑提取到受保护的字段,然后在测试中覆盖它。
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,但实际上我最近在类似的情况下这样做了,因为我想测试一些与正在加载的数据完全无关的功能,所以它非常有效。我还发现这样的成员暴露在其他类似的情况下很有用,在这些情况下,我需要摆脱一些长期存在于类中的依赖关系。
但是,如果您正在编写框架,我会建议您不要使用此解决方案,除非您真的不介意将成员公开给框架的用户。
这有点骇人听闻,但我发现它非常有用。
@valters
我不同意您的说法,即测试不应破坏构建。测试应该表明应用程序没有为所测试的功能引入新的错误(并且发现的错误表明缺少测试)。
如果测试没有破坏构建,那么您很容易遇到新代码破坏构建并且暂时不知道的情况,即使测试覆盖了它。失败的测试应该是一个危险信号,必须修复测试或代码。
此外,允许测试不破坏构建将导致失败率缓慢上升,以至于您不再拥有一组可靠的回归测试。
如果测试经常中断的问题,这可能表明测试的编写方式过于脆弱(依赖于可能发生变化的资源,例如没有正确使用 DB Unit 的数据库,或外部 Web 服务)这应该被嘲笑),或者这可能表明团队中有开发人员没有给予测试适当的关注。
我坚信应该尽快修复失败的测试,就像修复无法尽快编译的代码一样。
我不确定你为什么会说一旦重构完成,单元测试就会被删除。实际上,您的单元测试套件应该在主构建之后运行(您可以创建一个单独的“测试”构建,它只是在构建主产品之后运行单元测试)。然后,您将立即查看一件中的更改是否会破坏其他子系统中的测试。请注意,这与在构建期间运行测试有点不同(正如一些人可能提倡的那样) - 一些有限的测试在构建期间很有用,但通常仅仅因为某些单元测试失败而“崩溃”构建是没有生产力的。
如果您正在编写 Java(可能是),请查看http://www.easymock.org/ - 可能有助于减少测试目的的耦合。
我已经阅读了有效地使用遗留代码,并且我同意它对于处理“不可测试”的代码非常有用。
有些技术只适用于编译语言(我正在研究“旧”PHP 应用程序),但我想说本书的大部分内容适用于任何语言。
重构书籍有时会假设代码在重构之前处于半理想或“维护意识”状态,但我工作的系统并不理想,并且被开发为“边走边学”应用程序,或作为某些所用技术的第一个应用程序(我不怪最初的开发人员,因为我是他们中的一员),所以根本没有测试,代码有时很乱。本书解决了这种情况,而其他重构书籍通常不会(嗯,没有到这种程度)。
我应该提一下,我没有从本书的编辑和作者那里收到任何钱;),但我觉得这很有趣,因为遗留代码领域缺乏资源(尤其是我的语言法语,但那是另一个故事)。