В моей программе я тестирую кнопку, которая при нажатии вызывает окно JOptionPane в приложении.
Однако, когда это окно появляется, он ожидает, что пользователь нажмет "OK", что означает, что тест не может продолжаться, если окно не закрыто вручную.
Я хочу, чтобы иметь возможность закрыть это окно программно, когда оно появляется в моем тесте, или вообще не открывать эти окна JOptionPane для этого теста.
То, что я обычно делаю в таком случае, заключается в том, чтобы убедиться, что пользовательский интерфейс не отображается в моем тесте. Просто проиллюстрируем это некоторым кодом. Пусть говорят, что диалог запускается где-то в Action
public class ActionWithUI extends AbstractAction{
@Override
public void actionPerformed( ActionEvent e ){
//... do some stuff
int result = JOptionPane.show... //show the dialog
//do some other stuff based on the result of the JOptionPane
}
}
Затем я бы реорганизовал мой код, чтобы показать JOptionPane
в своем собственном отдельном методе.
public class ActionWithUI extends AbstractAction{
@Override
public void actionPerformed( ActionEvent e ){
//... do some stuff
int result = askUserForInput();
//do some other stuff based on the result of the JOptionPane
}
int askUserForInput(){
return JOptionPane.show...
}
}
Теперь в моем тесте я могу протестировать пользовательскую версию этого действия, где я переопределяю метод askUserForInput
и просто возвращаю параметр "ОК" (или "Отмена",...). Это позволит избежать любых умных трюков в моем тесте и сохраняет мой код читаемым.
Другой подход, который я иногда использую, не вызывает JOptionPane.show
напрямую, но, скорее, убедитесь, что мой класс принимает в своем конструкторе класс делегата для отображения таких диалогов. В моем тесте я могу вставить макет вместо реального класса и перехватить вызов в пользовательский интерфейс.
Конечно, оба этих подхода фактически не проверяют правильность показа JOptionPane
. Но, видя, что это метод JDK, я действительно не чувствую необходимости проверять это. И это не похоже на то, что я обходила какую-то бизнес-логику. Я просто избавился от вызова JOptionPane.show
.
Если ни один из них не является вариантом, я обычно использую прослушиватель, подключенный к DefaultKeyboardFocusManager
. Когда компонент, который имеет фокус, меняется, я вижу, является ли это JOptionPane
(с использованием иерархии Swing) и удаляет его. Это получается неплохо, но все же не на 100% надежным. Поэтому (и, конечно, для нового кода), я стараюсь придерживаться одного из двух ранее намеченных подходов
Существует два основных способа преодоления этой проблемы. Оба требуют, чтобы вы могли найти кнопку.
Это потребует, чтобы иметь возможность использовать вторую Thread
чтобы найти окно/диалог и пройти контейнер, чтобы найти кнопку...
В зависимости от того, как настроены ваши тесты, будет зависеть от того, сколько дополнительной работы вам потребуется пройти. Пользовательский интерфейс должен работать в Event Диспетчерской тему, если тесты выполняются в том же потоке, то вам нужно будет использовать отдельную Thread
, так как JOptionPane
будет блокировать EDT (особым образом), что делает невозможным для вашего кода для запуска, пока панель параметров не будет закрыта.
Если ваши тесты уже запущены в отдельном Thread
, у вас есть немного больше возможностей...
Это очень простой пример. Он инициализирует JOptionPane
в контексте EDT, но имеет методы, которые работают в основном потоке, которые находят и выполняют кнопку "ОК" диалогового окна сообщения.
import java.awt.Button;
import java.awt.Component;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.Window;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class FindButton {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JOptionPane.showMessageDialog(null, "This is message dialog", "Message", JOptionPane.INFORMATION_MESSAGE);
}
});
JDialog frame = waitForDialog("Message");
System.out.println("Found window " + frame);
if (frame != null) {
final JButton btn = getButton(frame, "OK");
System.out.println("Found button " + btn);
if (btn != null) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
btn.doClick();
}
});
}
}
}
public static JDialog waitForDialog(String title) {
JDialog win = null;
do {
for (Window window : Frame.getWindows()) {
if (window instanceof JDialog) {
JDialog dialog = (JDialog) window;
System.out.println(dialog.getTitle());
if (title.equals(dialog.getTitle())) {
win = dialog;
break;
}
}
}
if (win == null) {
try {
Thread.sleep(250);
} catch (InterruptedException ex) {
break;
}
}
} while (win == null);
return win;
}
public static JButton getButton(Container container, String text) {
JButton btn = null;
List<Container> children = new ArrayList<Container>(25);
for (Component child : container.getComponents()) {
System.out.println(child);
if (child instanceof JButton) {
JButton button = (JButton) child;
if (text.equals(button.getText())) {
btn = button;
break;
}
} else if (child instanceof Container) {
children.add((Container) child);
}
}
if (btn == null) {
for (Container cont : children) {
JButton button = getButton(cont, text);
if (button != null) {
btn = button;
break;
}
}
}
return btn;
}
}
В этом примере используется метод doClick
для JButton
для имитации нажатия кнопки. Вы можете использовать информацию locationOnScreen
с помощью кнопки и java.awt.Robot
чтобы физически нажать кнопку. но это просто показалось проще.
Вы также можете взглянуть на Jemmy, которая является библиотекой утилиты, предназначенной для упрощения тестирования приложений Swing (и JavaFX)...
waitForDialog
также должен вызываться в EDT, так как он работает с компонентами Swing. И если вы это сделаете, вы облажались ...
Сначала создайте JDialog
из объекта JOptionPane
. Затем создайте timer
для запуска в течение времени, которое вы хотите (пример 1 мин), и удалите dialog
после его завершения. Затем извлеките выбранное значение из объекта JOptionPane
, убедившись, что учтено неинициализированное значение, если диалог был удален вашим таймером.
final JOptionPane pane = new JOptionPane(......., JOptionPane.OK_CANCEL_OPTION);
final JDialog dialog = pane.createDialog(.....);
Timer timer = new Timer(you_time, new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
dialog.dispose();
}
});
timer.start();
dialog.setVisible(true);
dialog.dispose();
Integer choice = (Integer) (pane.getValue() == JOptionPane.UNINITIALIZED_VALUE ? JOptionPane.OK_OPTION : pane.getValue());
JOptionPane
если необходимо, с помощью дополнительного рефакторинга. Вы просто не показывали бы это в диалоговом окне блокировки