现在,我们已经建立了登录功能,并用单元测试覆盖了他,但没有可视的GUI来显示它。那下一步该做什么呢?对于已经作的和测试的实际功能,在GUI方面做的是创建和显示图像元素,然后在适当的时候调用login()方法。这个功能是普通和容易建立的,所以他不包含能中断和需要单元测试的复杂行为。因此,当建立GUI元素时,我们不需要去做测试先行的开发。例5展示了创建对话框窗口的Swing类LoginDialogView ,他的实现在LoginDialogView.java.文件。
LoginDialogView.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class LoginDialogView extends JFrame implements ActionListener {
protected JTextField usernameField;
protected JTextField passwordField;
protected JButton loginButton;
protected JButton cancelButton;
private LoginDialog dialog;
LoginDialogView(LoginDialog dlg) {
super("Login");
setSize(300, 140);
dialog = dlg;
addControls();
loginButton.addActionListener( this );
cancelButton.addActionListener( this );
}
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand();
if (cmd.equals("Login") && dialog.login(usernameField.getText(), passwordField.getText())) { hide(); } }
private void addControls() {
Container contentPane = this.getContentPane();
contentPane.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
JLabel label1 = new JLabel("Username:", Label.RIGHT);
c.insets = new Insets(2, 2, 2, 2); c.gridx = 0;
c.gridy = 0; contentPane.add(label1, c);
usernameField = new JTextField("", 60);
usernameField.setMinimumSize(new Dimension(180, 30));
c.gridx = 1;
contentPane.add(usernameField, c);
JLabel label2 = new JLabel("Password:", Label.RIGHT);
c.gridx = 0;
c.gridy = 1;
contentPane.add(label2, c);
passwordField = new JTextField("", 60);
passwordField.setMinimumSize(new Dimension(180, 30));
c.gridx = 1;
contentPane.add(passwordField, c);
loginButton = new JButton("Login");
c.gridx = 0;
c.gridy = 2;
contentPane.add(loginButton, c);
cancelButton = new JButton("Cancel");
c.gridx = 1;
contentPane.add(cancelButton, c); }}
LoginDialogView 包含了文本域,标签,和按钮元素。除了普通的GUI行为外,他只是有一个简单的行为,被actionPerformed() 方法实现。这个行为就是当登录按钮被点击后,login()方法被调用。如果登录成功,对话框就被所调用的hide()方法所关闭。
为了调用login()函数,在LoginDialogView 构造器里需要接收一个LoginDialog实例。另外,他组装了完整的GUI设置和事件处理代码。大部分代码在addControls() 里,他简单的创建和排版了窗体上的GUI元素。
LoginDialogView 代码示范了一个GUI瘦视图元素怎样被设计使它只包含普通的GUI代码,而把重要的需要测试应用的行为放到一个单独,可测试的敏捷对象中。
LoginDialogView 只需要通过创建它来测试,察看他,从用户的角度确认它看起来和运行起来象期望的那样。例6展示了可执行的类APPMain,它创建了对话窗体来传递可用性测试(指的是传递loginDialog的实例)。
AppMain.java
public class AppMain {
public static void main(String[] args)
{ AppMain app = new AppMain();
}
public AppMain() {
LoginDialog dialog = new LoginDialog();
LoginDialogView view = new LoginDialogView(dialog);
view.show();
while (view.isVisible()) {
try { Thread.currentThread().sleep(100);
}
catch(Exception x) {}
}
System.exit(0);
}
}
AppMain 类简单的创建一个LoginDialog 和LoginDialogView ,显示视图,休眠直到视图关闭,然后退出。
AppMain 象下面一样运行
java –classpath "." AppMain
运行它创建登录对话框,如图4所示
Figure 4. The login dialog window
和登录对话框交互验证了用图4所示的值登录,会登录成功然后窗体关闭。试着用其它的值登录,窗体将保持打开,因为登录失败了。取消按钮关闭窗体,就象窗体的关闭按钮一样。这个登录对话框就如同设计的那样运行。
解决方案
我们已经根据TDD创建了登录对话框和一个敏捷对象/瘦视图设计模式。得到了一个有很好构架和功能的程序。有功能的应用行为被单元测试所覆盖,普通的用来显示的代码不需要复杂的GUI测试。图5展示了我们所开发的这个软件的构架。
Figure 5. The classes LoginDialog, LoginDialogView, and LoginDialogTest
基于此,其他的特性可以被加入。登录对话框可以有一个消息域去提醒用户登录失败。其他的登陆参数域也可以被加入。一个单独的验证对象可以被创建,硬编码的登录值可以被删掉。不管怎么变化,TDD和敏捷对象/瘦视图模式提供了一个设计和实现上的清晰的方向。重要的应用功能是在于可以测试的敏捷的对象,和在瘦视图中普通的显示用的代码的。
要找更多的测试驱动GUI开发的详细例子,JUnit和其他xUnit测试框架的广泛覆盖,TDD,还有单元测试策略,请看我的书Unit Test Frameworks ,2004年11月O'Reilly出版。