Тестирование Swing приложений с помощью Jemmy Toolkit v.2

Swing приложения

Создадим простое Swing приложение, как показано на рисунке:

Для создания Swing приложений нужно подключить соответствующие библиотеки:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

Далее в конструкторе SwingApplication() :
1. Создадим объект JFrame:

JFrame myFrame = new JFrame("Button Label Text");

2. Установим для него разметку в виде таблицы GridLayout:

myFrame.setLayout(new GridLayout(0,2,5,5));

где 0 - любое число строк, 2 - число столбцов , 5 зазоры по вертикали и по горизонтали.

3. Расположим компоненты на JFrame:

JLabel myLabel = new JLabel("");
myFrame.add(myLabel);
JTextField myTextField = new JTextField(20);
myFrame.add(myTextField);
JButton myBut = new JButton("First");
myFrame.add(myBut);

4. Добавим обработку действий (событий) таким компонентам, как JTextField и JButton:

SwingApplication(){
...
myTextField.setActionCommand("field1");
myTextField.addActionListener(this);
myBut.addActionListener(this);
...
}//Окончание конструктора
/**Добавляем метод обрабатывающий события кликов, ввода текста и т.д.
*/
public void actionPerformed(ActionEvent e) {
  if(e.getActionCommand().equals("First")){
    myTextField.setText("First pressed");
  }
  else if(e.getActionCommand().equals("field1")){
    myLabel.setText(myTextField.getText());
  }
}

В класе SwingApplication создадим публичный метод, который стартует данное приложение startApp() :

/**
* Method for starting SwingApplication
*/
public static void startApp(){
  SwingUtilities.invokeLater(new Runnable(){
    public void run(){
      new SwingApplication();
    }
  });
}

Полный код приложения SwingApplication.java.

Для запуска приложения из командной строки, проверяем переменную CLASSPATH:

# env|grep CLASSPATH
CLASSPATH=./:/home/JavaClasses:/home/JavaClasses/jemmy-2.2.7.5.jar:/home/JavaClasses/junit4-4.5.jar

Заметьте jemmy-2.2.7.5.jar и junit4-4.5.jar нам будут нужны для запуска тестов, не забудьте их тоже прописать.
Теперь копируем класс ( с пакетом) в эту папку и компилируем

# javac /home/JavaClasses/swing/SwingApplication.java

Запускаем для проверки откомпилированный java-code

# java swing.SwingApplication

Тестирование с помощью сценариев Jemmy v2

Jemmy позволяет искать компоненты на фрейме ( диалоге), создавать события такие как, а также считывать значения из компонентов, таких как Text, Title и т.д.
Компоненты можно искать с помощью методов:
Чем больше параметров Вы знаете о компоненте, тем проще Вам его будет потом найти. А именно, можно найти JButton следующими способами:

Для использования возможностей Jemmy, необходимо сначала подключить нужные библиотеки:

import org.netbeans.jemmy.*;
import org.netbeans.jemmy.operators.*;

Как следует из названия, каждый тест должен быть потомком класса Scenario. Удобнее всего создавать тесты в каком-нибудь IDE.
Создадим пакет test_scenario и создадим в нем Java class. Сделаем его наследником класса Scenario:

package test_scenario;
import swing.*;
import org.netbeans.jemmy.*;
import org.netbeans.jemmy.operators.*;
 /**Testing class by Jemmy 2 toolkit
  *
  * @author Shpatserman Maria
  */
  public class  SwingApplicationScenario implements Scenario{
  }

Этот класс должен переопределять метод родительского класса runIt() . Внутри этого метода мы опишем тест, реализующий поиск всех нужных компонентов, нажатие кнопок, ввод текста и т.д. Получим метод следующего вида:

public int runIt(Object param) {
  try {
    //Запуск Swing приложения
    new ClassReference("swing.SwingApplication").startApplication();
    //Поиск JFrame
    JFrameOperator mainFrame = new JFrameOperator("SwingApplication");
    //Делаем небольшую задержку
    new QueueTool().waitEmpty(200);
    //Ищем кнопку по имени
    JButtonOperator firstButton = new JButtonOperator(mainFrame, "First");
    firstButton.push();
    //Ищем JTextField по имени после нажатия кнопки должно стать "First pressed"
    JTextFieldOperator textField = new JTextFieldOperator(mainFrame,"First pressed");
    //Очищаем поле ввода
    textField.clearText();
    textField.enterText("Hello");        
    //После ввода текста на JFrame должен появиться JLabel
    //Метка должна быть "Hello"
    new JLabelOperator(mainFrame,"Hello");
    //Поиск второго компонента JTextField по индексу
    JTextFieldOperator textField2 = new JTextFieldOperator(mainFrame,1);
    textField2.enterText("Field2");
    //Опять ищем JLabel по тексту
    new JLabelOperator(mainFrame,"Field2");        
    JButtonOperator secondButton = new JButtonOperator(mainFrame, "Second");
    secondButton.push();           
    mainFrame.close();
  }
  catch(Exception e) {
    e.printStackTrace();
	 return(1);
  }
  return(0);
}

Для запуска теста делаем класс исполняемым. Создаем метод main:

public static void main(String [] argv){
  String[] params = {"test_scenario.SwingApplicationScenario"};
  org.netbeans.jemmy.Test.main(params);
}

Полный код класса SwingApplicationScenario.java
Компилируем класс:

Запускаем тест из командной строки:

Наименование окон кнопок и других компонентов можно задавать не только напрямую в тексте программы, но и считывать значения из файла. Для этого используем объект класса Bundle и его метод getResource():

...
Bundle bundle = new Bundle();
bundle.loadFromFile("/home/java/properties_for_test");
//wait frame
JFrameOperator mainFrame = new JFrameOperator(bundle.getResource("main_window"));
...

Пример файла с заданными параметрами: properties_for_test.
Класс использующий передачу параметров из файла: ScenarioByFile.java.

Тестирование с помощью JUnit4 framework

С помощью Unit-тестов нужно обязательно тестировать внешние (public) методы, а также API. В нашем случае мы напишем Unit-тесты для тестирования GUI.
Теперь обернем все пункты из сценариев в небольшие, независимые тесты. Чем меньше будет Unit-тест, тем проще его будет отлаживать. Название каждого Unit-теста сделаем подробным и читаемым. Не скупитесь на комментарии.
Основные ключевые слова(обозначения) в Unit-тестах следующие:

Методы обозначенные как @Before/@After должны быть public. Методы обозначенные как @BeforeClass/@AfterClass должны быть public static. В блок @Before завернем создание тестового приложения:

@Before
public void getFrame(){
  //Создаем приложение
  SwingApplication.startApp();
  //Ищем фрейм
  mainFrame=new JFrameOperator();
  //Это приостановление очереди выполнения событий
  //просто для того, чтобы было удобнее смотреть
  mainQueue = new QueueTool();
  mainQueue.waitEmpty(200);
}

В блок @BeforeClass обернем временные настройки - ожидание окна, ожидание компонентов и т.д. Если за указанные промежутки времени компоненты не будут найдены, то тест будет провален.

@BeforeClass
public static void setTimeouts(){
  //Скорость набора символов
  JemmyProperties.setCurrentTimeout("JTextComponentOperator.PushKeyTimeout",   50);
  //Максимальное время ожидания JFrame
  JemmyProperties.setCurrentTimeout("FrameWaiter.WaitFrameTimeout", 2000);
  //Максимальное время ожидания всех компонентов
  JemmyProperties.setCurrentTimeout("ComponentOperator.WaitComponentTimeout", 1000);  
}

Теперь напишем простой тест, определяющий корректность названия нашего окна JFrame:

@Test
public void testTitleFrame(){
  //У найденного ранее фрейма берем параметр Имя окна
  String titleFrame = mainFrame.getTitle();
  //Сравниваем с ожидаемым значением
  assertEquals("Button Label Text",titleFrame);
  mainQueue.waitEmpty(100);
}

Каждый Unit-тест должен иметь утверждающие методы, такие как:

И многие другие.
Аналогично обернем в тест нажатие кнопок и проверку корректности значений в полях. Как было видно из п.4 ( разработки Swing приложения) при нажатии на кнопку First в поле myTextField должен был появиться текст "First pressed". Это мы и будем использовать, как проверочное условие.

@Test
public void testActionButtons(){
  //Теперь находим кнопку по её имени First
  JButtonOperator butn1 = new JButtonOperator(mainFrame,"First");
  //Осуществляем клик 
  butn1.push();   
  JTextFieldOperator LeftField = new JTextFieldOperator(mainFrame,0);
  //Сравниваем значение поля с ожидаемым
  assertTrue("After First Button push LeftField should " +
             "contained \"First pressed\" string",LeftField.getText().equals("First pressed"));
  mainQueue.waitEmpty(500);
  JButtonOperator butn2 = new JButtonOperator(mainFrame,"Second");
  butn2.push();
  JTextFieldOperator RightField =  new JTextFieldOperator(mainFrame,1);
  //Делаем проверку на НЕ совпадение значений
  assertFalse("After Second Button push RightField should " +
              "not contain \"AAA\" string ",RightField.getText().equals("AAA"));
  mainQueue.waitEmpty(500);
}

Добавим аналогичный тест для проверки реакции на ввод текста в поля и обновления JLabel. А также тест, который смотрит все ли компоненты присутствуют. Полный исходный код тестового класса TestSwingApplication.java
Запускаем тестовый класс в IDE Netbeans 6.5:

Компилируем тест( должен лежать в той же папке что и тестируемое приложение):

# javac /home/JavaClasses/swing/TestSwingApplication.java

Запускаем:

# java org.junit.runner.JUnitCore swing.TestSwingApplication

Результат:

Замечание: В новой версии JUnit 4 Вам не надо делать тестовые классы наследниками Test Case, как было в версии JUnit 3.

Исходный код проекта Swing с использованием Jemmy ver.2 для тестирования GUI : SwingUnitJemmyTests.tar.gz.
jar файл классов для выполнения примеров: Swing_UnitTests.jar.
Тестовый файл настроек: properties_for_test.
Более подробно о Jemmy Toolkit можно прочитать здесь.