测试遗留代码

发表于:2007-10-15 16:56

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:Elliotte Harold    来源:网络转载

  Test-first 编程是自面向对象编程以来最有效的编码方式,但它假定您从一个空白屏幕开始编程。当代码已经存在时,您应该怎么做呢?使用一个流行的开放源码的 Java™ 工具作为例子,作者 Elliotte Rusty Harold 向您展示了如何为从未测试过的遗留代码开发测试套件。
/* I have no idea how this works but it seems to.  Whatever you 
   do, don't touch this function, and don't break this code!  
 (虽然我不知道这段代码起什么作用,但是看上去它似乎是有用的。无论您做什么,一定不要碰这个函数,不要破坏这段代码!)
*/
    如果您曾经遇到过带有此类注释的代码,这种情况并不少见。因为没有人了解这些系统,所以有时候就使用规则约束禁止进入整个系统;但是仍然需要对这些系统进行维护。即使是一个已经完全没有 bug 的系统(又有哪个系统能够完全没有 bug?),外部环境的改变也会使代码的改变成为必要。Y2K 营业额是一个最大且最明显的例子。欧元的引入对于某些金融系统来说相当于造成外伤。Sarbanes-Oxley 引入了新的以前不存在的报告要求,而且为了支持这些新规则,必须对遗留软件进行翻新。这个世界不是静态的,所以软件也不能是静态的,它必须向前发展否则就会被代替。

  好消息是,测试驱动开发不仅仅适合于新代码。即使是程序员维护老代码时也可以利用它编写、运行以及通过测试。对于已经在生产中的遗留系统,测试确实更加 重要。只有通过测试,您才能确信您对于系统中的某一部分所做的改变不会中断其他地方的另外一部分。当然,您可能没有时间或者经费为一个规模庞大的代码基础达到 100% 的测试覆盖率,但是即使是并不完美的覆盖率也能减少失败的风险,加速开发并且产生更加健壮的代码。

  本文使用 jEdit 做为例子,向您展示如何为从未测试过的遗留代码开发一个单元测试套件。jEdit 是一个流行的开放源码的文本编辑器,它完全没有任何测试套件!但是我将马上开始对其进行修改。在本文中,我着手开发一个测试套件,其目的是为了使将来 jEdit 的开发更加多产、高效并且有趣。

第一次测试

  中国有句老话,千里之行始于足下。遗留代码的测试套件首先开始于一个单独的测试。重点是做什么和从何做起。不要掉入相信因为不能够测试每行代码所以就不能够测试任何东西的陷阱。只管打开您的 IDE 并且开始编写测试。使用 JUnit(或者 NUnit,或者 CppUnit,或者任何您喜欢的框架)和一个一般的 IDE,您通常就能够在 20 分钟以内编写出第一个测试。编写测试要比编写模型代码简单得多。测试很小并且具有独立的代码块。它们不需要很多配置、思考和理解。您不需要 “专业知识” 来艰难地编写出高质量的测试。

  测试套件需要做的第一件事情是直接到达方法的中心。寻找您能够做的最大范围、最全面的测试。对于一个独立的应用程序,可能是 main() 方法。例如,这是我的第一个 jEdit 测试用例。它所做的就是运行应用程序的 main() 方法并且检验它是否在屏幕上输出了正确的窗口:


清单 1. 测试 jEdit 的 main() 方法
import java.awt.Frame;
import junit.framework.TestCase;
public class MainTest extends TestCase {
    public void testMain() {
        org.gjt.sp.jedit.jEdit.main(new String[0]);
        // make sure there's a window on the screen
        Frame[] allFrames = Frame.getFrames();
        for (int i = 0; i < allFrames.length; i++) {
           Frame f = allFrames[i];
           if (f.isFocused()) {
              assertTrue(f instanceof org.gjt.sp.jedit.View);
           }
        }
    }
}


  第一个测试的目的不是在边界条件上费力,也不是为了查看解决了什么问题。第一个测试是一个发烟试验,目的是为了对于什么可能是错误的给您一个清晰的概念。即使最基本的测试也不能揭示出构造系统、运行时环境、已安装的软件以及对每件事情进行本质上的破坏的其他主要问题中存在的问题。我的第一个测试用例确实能够准确地发现 jEdit 代码基础的这样一个问题:在我的类路径中没有包含所有可能的目录。

  我并没有开始测试类路径配置,但是我寻找到的问题也是重要的,因为它可能导致代码基础很难调试。类似这种的全面测试涉及到应用程序的很多方面。很多不同的东西能够中断并且导致测试失败。就这种意义上说,并不是非常统一。在 test-first 编程中,这不是一个问题;但是当测试遗留代码时,您没有时间或者预算为每个单独的方法或者分支编写独立的测试。您必须在编写每个测试时尽量地覆盖尽可能多的方法和分支。使用一些测试来测试大部分代码比根本不进行测试要好。

对 main() 方法进行故障诊断

  测试 main() 方法并不作用于所有的应用程序。例如,库中不包含 main() 方法。一些确实具有 main() 方法的应用程序可能也不希望这个方法被不止一次的调用。如果您这样做的话,静态初始化软件会非常混乱。它们可能不去清除一些对象和类,因为它们假定当程序存在时,虚拟机也存在,所有对象和类都将被自动清除。如果事实如此,您可能需要更深层地观察您的应用程序,寻找第一个测试点。

  但是,不要走的过深。对于相对于 test-last 开发来说的 test-first 开发,尤其是对于遗留代码来说,您可能会遇到一个问题是,经常存在未公开的依赖关系和先决条件。一些方法假定其他对象存在并且在它们运行前已经创建了。例如,大多数菜单栏不会脱离它们的父窗体单独起作用。

  实际上,如果您试着不止一次地调用 main() 方法,jEdit 就会变得非常混乱。我希望能一次也不调用它。但是,很多其他代码依赖于 jEdit.initSystemProperties() 方法已经被调用,并且这个方法是私有的。执行它的惟一方法就是调用 main()。我采用的解决方法是,只有当 main() 方法一次也没被调用过的时候才调用它,如下所示:

private static boolean hasMain = false;
 protected void setUp() {
    if (!hasMain) {
        jEdit.main(new String[0]);
        hasMain = true;
    }
    View view = jEdit.getFirstView();
    while (view == null) {
        // First window may take a little while to appear
        view = jEdit.getFirstView();
    }
    menubar = view.getJMenuBar();
   }

 

31/3123>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号