使用Abbot给Java Swing写单元测试,遇到这样一个问题:如果用到了showDialog(...)方法,由于是ModelDialog,系统执行到这里就被block了,无法通过Abbot写单元测试。
举个简单的例子来说:Frame中有个button,点击后会显示JColorChooser Dialog,选取颜色后点击OK或者Cancel按钮,Dialog消失,同时返回Color对象,然后就可以在frame中修改Label的颜色。
所以想象中的单元测试应该是这样:
@Test
public void testSelectColorRedAndClickOKButton() throws Exception {
FrameDemo demo = new FrameDemo();
demo.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
demo.pack();
demo.setVisible(true);
JButton colorButton = (JButton) getFinder().find(demo, new ClassMatcher(JButton.class))
new JMenuItemTester().actionClick(colorButton);//1. Here will block after JColorChooser shows
// 2. Some action to select color red: JColorChooserTester().actionSelectColor(Color.red)
JLabel label = (JLabel) getFinder().find(demo, new ClassMatcher(JButton.class))
assertEquals(Color.red, label.getForeground());
}
类FrameDemo很简单:
public class FrameDemo extends JFrame{
public void FrameDemo(){
JLabel label = new JLabel("Text color will change after you select new color");
label.setForeground(Color.black);
JButton colorButton = new JButton("Choose Color...");
JPanel panel = new JPanel();
panel.add(label);
panel.add(colorButton);
this.setContentPane(panel);
}
}
但是有两个问题:
1.注释1所在地方,当JColorChooser Dialog显示之后线程就被block了;
2.Abbot中没有类JColorChooserTester帮助选择颜色。
根据以上考虑,决定自己写一个JColorChooserTester,完成以下功能:
1.根据输入选择颜色,并点击OK按钮关闭对话框;
2.可以不选取颜色直接点击Cancel按钮关闭对话框;
3.应该在显示对话框之前就调用该方法。
根据这些分析,对上面的测试做了修改:
@Test
public void testSelectColorRedAndClickOKButton() throws Exception {
FrameDemo demo = new FrameDemo();
demo.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
demo.pack();
demo.setVisible(true);
JButton colorButton = (JButton) getFinder().find(demo, new ClassMatcher(JButton.class));
new JColorChooserTester().actionSelectColor(demo, Color.red);// We want dialog to return red
new JMenuItemTester().actionClick(colorButton);
JLabel label = (JLabel) getFinder().find(demo, new ClassMatcher(JButton.class));
assertEquals(Color.red, label.getForeground());
}
测试写完了,接下来就写JColorChooserTester类,上面的测试能够通过,并经过简单的重构后:
package com.tw.ui.tester;
import abbot.finder.BasicFinder;
import abbot.finder.Matcher;
import abbot.finder.ComponentNotFoundException;
import abbot.finder.MultipleComponentsFoundException;
import abbot.finder.matchers.ClassMatcher;
import abbot.tester.JComponentTester;
import abbot.tester.JMenuItemTester;
import javax.swing.*;
import java.awt.*;
import org.apache.log4j.Logger;
public class JColorChooserTester extends JComponentTester {
private static final long DEFAULT_TIME_OUT_IN_MILLISECONDS = 5000;
private long waitInMilliSeconds;
private Logger logger = Logger.getLogger(JColorChooserTester.class);
public JColorChooserTester() {
this(DEFAULT_TIME_OUT_IN_MILLISECONDS);
}
public JColorChooserTester(long waitInMilliSeconds) {
this.waitInMilliSeconds = waitInMilliSeconds;
}
public void actionClickCancel(final Container container) {
final JColorChooserFinder chooserFinder = new JColorChooserFinder(container);
Thread thread = new Thread(){
public void run(){
new JMenuItemTester().actionClick(chooserFinder.getCancelButton());
}
};
thread.start();
}
public void actionSelectColor(Container container, final Color returnedColor) {
final JColorChooserFinder chooserFinder = new JColorChooserFinder(container);
Thread thread = new Thread(){
public void run(){
chooserFinder.getColorChooser().setColor(returnedColor);
new JMenuItemTester().actionClick(chooserFinder.getOKButton());
}
};
thread.start();
}
class JColorChooserFinder {
JColorChooser colorChooser;
JButton cancelButton;
JButton okButton;
private boolean shouldWaitForDialog = true;
long start = System.currentTimeMillis();
public JColorChooserFinder(Container container) {
FindDialog(container);
}
private void FindDialog(final Container container) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
JDialog dialog = waitAndFindDialog(container);
if (dialog != null) {
logger.debug("Find JColorChooser Dialog.");
cancelButton = findCancelButton(dialog);
okButton = findOKButton(dialog);
colorChooser = findColorChooser(dialog);
}
} catch (Exception e) {
}
finally {
shouldWaitForDialog = false;
}
}
});
}
private JDialog waitAndFindDialog(final Container container) {
JDialog dialog = null;
long waited = 0;
while (dialog == null && shouldWaitMore(waited)) {
try {
dialog = (JDialog) new BasicFinder().find(container, new ClassMatcher(JDialog.class));
} catch (ComponentNotFoundException e) {
} catch (MultipleComponentsFoundException e) {
logger.debug("Found multiple JDialogs");
break;
}
waited += waitMore(500);
}
return dialog;
}
public JButton getCancelButton() {
long waited = 0;
while (shouldWaitForDialog && shouldWaitMore(waited)) {
waited += waitMore(500);
}
return cancelButton;
}
private long waitMore(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
}
return millis;
}
public JButton getOKButton() {
long waited = 0;
while (shouldWaitForDialog && shouldWaitMore(waited)) {
waited += waitMore(500);
}
return okButton;
}
public JColorChooser getColorChooser() {
long waited = 0;
while (shouldWaitForDialog && shouldWaitMore(waited)) {
waited += waitMore(500);
}
return colorChooser;
}
private JButton findCancelButton(JDialog dialog) {
return findButtonByText(dialog, UIManager.getString("ColorChooser.cancelText"));
}
private JButton findOKButton(JDialog dialog) {
return findButtonByText(dialog, UIManager.getString("ColorChooser.okText"));
}
private JColorChooser findColorChooser(JDialog dialog) {
try {
return (JColorChooser) new BasicFinder().find(dialog, new ClassMatcher(JColorChooser.class));
} catch (Exception e) {
return null;
}
}
private JButton findButtonByText(JDialog dialog, final String cancelString) {
try {
return (JButton) new BasicFinder().find(dialog, new Matcher() {
public boolean matches(Component component) {
return component.getClass().equals(JButton.class) && ((JButton) component).getText().equals(cancelString);
}
});
} catch (Exception e) {
return null;
}
}
private boolean shouldWaitMore(long waited) {
return waited < waitInMilliSeconds;
}
}
}
JColorChooserTester的源码和测试代码请见附件。