Как автоматически нажать кнопку ОК на JOptionPane при тестировании?

1

В моей программе я тестирую кнопку, которая при нажатии вызывает окно JOptionPane в приложении.

Однако, когда это окно появляется, он ожидает, что пользователь нажмет "OK", что означает, что тест не может продолжаться, если окно не закрыто вручную.

Я хочу, чтобы иметь возможность закрыть это окно программно, когда оно появляется в моем тесте, или вообще не открывать эти окна JOptionPane для этого теста.

Теги:
testing
swing
popup
joptionpane

3 ответа

2

То, что я обычно делаю в таком случае, заключается в том, чтобы убедиться, что пользовательский интерфейс не отображается в моем тесте. Просто проиллюстрируем это некоторым кодом. Пусть говорят, что диалог запускается где-то в 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% надежным. Поэтому (и, конечно, для нового кода), я стараюсь придерживаться одного из двух ранее намеченных подходов

  • 0
    Проблема, с которой я сталкиваюсь, заключается в том, что вы потенциально изменили состояние тестируемого приложения ... возможно, сделали тест недействительным ...
  • 0
    @MadProgrammer Я думаю, что поиск кнопки ОК и ее нажатие также совершенно бесполезны. Обратите внимание, что в моем подходе вы все равно можете визуализировать содержимое JOptionPane если необходимо, с помощью дополнительного рефакторинга. Вы просто не показывали бы это в диалоговом окне блокировки
Показать ещё 3 комментария
1

Существует два основных способа преодоления этой проблемы. Оба требуют, чтобы вы могли найти кнопку.

Это потребует, чтобы иметь возможность использовать вторую 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)...

  • 0
    Я бы сказал, что ваш waitForDialog также должен вызываться в EDT, так как он работает с компонентами Swing. И если вы это сделаете, вы облажались ...
  • 0
    @ Робин, проблема в том, что JOptionPane блокирует. Вы не можете использовать waitForDialog в EDT, потому что он сам блокирует (см. Thread.sleep), поэтому у нас есть ловушка 22. Поскольку мы не изменяем состояние пользовательского интерфейса, это "немного" безопаснее, но вы может все еще столкнуться с состоянием гонки, но если мы постоянно проверяем состояние, это становится менее важной проблемой. Если вы действительно обеспокоены, вы можете использовать invokeAndWait, но меня беспокоит тот факт, что JOptionPane блокирует EDT (по-своему), что может помешать его возвращению
Показать ещё 3 комментария
0

Сначала создайте 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());
  • 0
    Это потребовало бы, чтобы вы знали, что приложение работает в «тестовом режиме», который изменяет состояние программы, изменяет способ ее работы и рискует сделать тест недействительным ...

Ещё вопросы

Сообщество Overcoder
Наверх
Меню