对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();
}
非常重要的是,hasMain域是静态的。jEdit为每个测试方法构造了一个新的测试用例对象,所以只有静态域才能保存每个套件状态。
如果能够自由地重构正在测试的代码,您的工作会简单些。特别是将一些私有方法变成公有方法能够使这个代码编写起来更容易。在test-first开发中,这些都不成问题,因为您倾向于将代码编写得易于测试。然而,遗留代码几乎不考虑可测试性,因此,您必须消除这样的阻碍。
介绍装备
一旦编写了第一个测试,您就能够经常快速地从同一个框架下开发更多的测试。将初始化和清理代码放入setUp()和tearDown()方法中,注意从那里您可以真正快速地编写多少测试。例如,我编写过一些基础测试来保证jEdit菜单栏出现并且在正确的地方显示正确的菜单,如清单 2 所示。
清单2 测试jEdit菜单
package org.jedit.test;
import javax.swing.*;
import org.gjt.sp.jedit.*;
import junit.framework.TestCase;
public class MenuTest extends TestCase {
private JMenuBar menubar;
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();
}
public void testFileIsFirstMenu() {
JMenu file = menubar.getMenu(0);
assertEquals("File", file.getText());
}
public void testEditIsSecondMenu() {
JMenu edit = menubar.getMenu(1);
assertEquals("Edit", edit.getText());
}
public void testHelpIsLastMenu() {
JMenu help = menubar.getMenu(menubar.getMenuCount()-1);
assertEquals("Help", help.getText());
}
// Tests for other menus...
}
与典型的test-first编程不同,我在此处编写了很多测试代码却没有必要编写任何模型代码。标准的TDD只编写能够使一个测试失败的足够多的代码。然后,它就切换到模型代码中直到测试通过为止。我并不是说TDD原则是错误的,但当两百万行的遗留模型代码已经存在时,它的确不能成为一个有用的选择。那时的目的是为了尽快地获得尽可能大的覆盖率。
您可能已经注意到我的测试的另外一个问题了。jEdit是国际化的,所以测试也应该是国际化的。也就是说像“File”和“Edit”这样的字符串应该被分离放入资源束中,这样测试将在地方化的系统上通过,在这些系统中,它们可能有其他的名字,如“Fichier”、“Edition”和“Aide”。这并不难做到,但是与本题不太相关,所以我在另一个时间对其进行讲解。
