Модульное тестирование GUI (графического интерфейса пользователя) с помощью Qt Test. Часть 2. Расширенное тестирование
В данном руководстве рассматриваются более сложные темы модульного тестирования графического интерфейса пользователя с помощью Qt Test, фреймворка Qt для модульного тестирования кода на C++. Обсуждается и подробно анализируется рабочий пример. Предоставляется полный проект qmake и исходный код на C++.
Больше модульного тестирования GUI с помощью Qt Test
В данном руководстве будут представлены более продвинутые функции Qt Test, посвященные модульному тестированию графического интерфейса. В частности, будет показано, как имитировать и обрабатывать фокус клавиатуры и как тестировать сигналы Qt при модульном тестировании GUI.
Это четвертый и последний пост из серии, посвященной Qt Test. Посты этой серии:
- Модульное тестирование кода на C++ с помощью Qt Test. Часть1. Введение
- Модульное тестирование кода на C++ с помощью Qt Test. Часть 2. Расширенное тестирование
- Модульное тестирование GUI (графического интерфейса пользователя) с помощью Qt Test. Часть 1. Введение
- Модульное тестирование GUI (графического интерфейса пользователя) с помощью Qt Test. Часть 2. Расширенное тестирование
Чтобы полностью понять новые концепции, которые я представлю здесь, рекомендую прочитать предыдущие посты этой серии, в частности первую часть модульного тестирования графического интерфейса с помощью Qt Test, .
Тестирование QWidget
В этом руководстве будет продолжено тестирование QWidget, представленного в части 1:
Виджет содержит 2 поля ввода и 2 кнопки. Нажатие кнопки CONCAT объединяет строки двух полей ввода и выводит результат на виджет надписи QLabel
. Нажатие кнопки CANCEL удаляет все видимые данные.
Единственное отличие от виджета, реализованного в части 1, заключается в том, что эта новая версия при каждом нажатии кнопки выдает сигнал. Это для эксперимента с проверкой сигнала, о чем вы скоро прочтете.
Тестирование фокуса
При тестировании фокуса с помощью Qt Test мы в основном имитируем взаимодействие пользователя с нашим виджетом с помощью клавиатуры.
При работе с фокусом важно учитывать то, что простой вызов QWidget::setFocus
для виджета не сработает, поскольку виджет не виден во время выполнения теста. Что нам нужно сделать, чтобы всё заработало, так это вызвать статическую функцию QApplication::setActiveWindow
на QWidget
, который мы тестируем, в данном случае объект PanelConcat
:
void TestPanelConcat::TestFocus()
{
// включает события фокуса и виджета
QApplication::setActiveWindow(&panel);
Теперь события фокуса будут успешно доставляться в наш виджет.
Дополнительный шаг при тестировании виджета – установить с помощью QWidget::setFocus
, какой элемент изначально будет удерживать фокус:
// устанавливаем начальный фокус
panel.mInputA->setFocus();
QVERIFY2(panel.mInputA->hasFocus(), "Input A doesn't have focus");
Убедившись, что фокус установлен правильно, мы можем продолжить симуляцию.
Эта анимированная гифка показывает то, что мы хотим имитировать. Обратите внимание на синюю подсветку вокруг элементов виджета, показывающую, где находится фокус.
Следующий код выполняет запись в 2 поля ввода с помощью функции QTest::keyClicks
, представленной в части 1. Для перехода от одного поля к другому код имитирует нажатие клавиши TAB, которая по умолчанию перемещает фокус на следующий виджет в цепочке фокуса. Наконец, фокус перемещается на кнопку CONCAT, которую нажимают с помощью клавиши SPACE.
// запись STR1
QTest::keyClicks(QApplication::focusWidget(), STR1);
// перемещение фокуса на следующий виджет
QTest::keyClick(&panel, Qt::Key_Tab);
QVERIFY2(panel.mInputB->hasFocus(), "Input B doesn't have focus");
// запись STR2
QTest::keyClicks(QApplication::focusWidget(), STR2);
// перемещение фокуса на следующий виджет
QTest::keyClick(&panel, Qt::Key_Tab);
QVERIFY2(panel.mButtonConcat->hasFocus(), "Button CONCAT doesn't have focus");
// нажатие кнопки CONCAT с помощью клавиши пробел
QTest::keyClick(QApplication::focusWidget(), Qt::Key_Space);
QCOMPARE(panel.mLabelRes->text(), STR_RES);
Ожидаемый результат всей этой симуляции – прочитать две строки, объединенные в виджете надписи результата.
В заключительной части теста фокус переместится на кнопку CANCEL, и он нажмет на нее:
// перемещение фокуса на следующий виджет
QTest::keyClick(&panel, Qt::Key_Tab);
QVERIFY2(panel.mButtonCancel->hasFocus(), "Button CANCEL doesn't have focus");
// нажатие кнопки CANCEL с помощью клавиши пробел
QTest::keyClick(QApplication::focusWidget(), Qt::Key_Space);
QVERIFY2(panel.mInputA->text().isEmpty(), "Cancel didn't work on input A");
QVERIFY2(panel.mInputB->text().isEmpty(), "Cancel didn't work on input B");
QVERIFY2(panel.mLabelRes->text().isEmpty(), "Cancel didn't work on res label");
}
Ожидаемый результат – пустой текст как в полях ввода, так и в виджете надписи, служащего для вывода результата.
Я протестировал поведение двух кнопок в одной функции для простоты, но при написании реальных модульных тестов лучше сохранять независимость. Следовательно, в реальном проекте было бы лучше разделить этот тест на две функции.
Тестирование сигналов
Одна из ключевых особенностей Qt – это объектная связь на основе сигналов и слотов, которая по сути является системой, основанной на событиях.
Для ее тестирования Qt Test предлагает класс под названием QSignalSpy
, который представляет собой QList
на стероидах, который может перехватывать и записывать сигналы.
Как и предполагалось во введении, класс PanelConcat
выдает два сигнала:
DataAvailable(QString)
– выдается после нажатия кнопки CONCAT и объединения входного текста.DataCleared()
– выдается после нажатия кнопки CANCEL и очистки всего текста.
В следующем тесте код будет имитировать нажатие двух кнопок и проверять выдаваемые сигналы.
Первая часть кода для простоты устанавливает текст непосредственно в поля ввода. А затем создаются два объекта QSignalSpy
, каждый из которых связан с сигналом, выдаваемым объектом panel
:
void TestPanelConcat::TestSignals()
{
// установка входных данных
panel.mInputA->setText(STR1);
panel.mInputB->setText(STR2);
// создание объектов шпионов
QSignalSpy spy1(&panel, &PanelConcat::DataAvailable);
QSignalSpy spy2(&panel, &PanelConcat::DataCleared);
Вторая часть имитирует нажатие кнопки CONCAT, а затем проверяет, записан ли сигнал DataAvailable
. Проверить это можно, просто определив, сколько элементов содержит соответствующий объект QSignalSpy
(spy1
). Затем последний QCOMPARE
проверяет, соответствует ли параметр, полученный из сигнала, строке результата.
// нажатие кнопки CONCAT
QTest::mouseClick(panel.mButtonConcat, Qt::LeftButton);
QCOMPARE(spy1.count(), 1);
QCOMPARE(spy2.count(), 0);
QList args = spy1.takeFirst();
QCOMPARE(args.at(0).toString(), STR_RES);
Последняя часть функции тестирования имитирует нажатие кнопки CANCEL и проверяет, получен ли сигнал DataCleared
, и что он не содержит никаких параметров.
// нажатие кнопки CANCEL
QTest::mouseClick(panel.mButtonCancel, Qt::LeftButton);
QCOMPARE(spy1.count(), 0);
QCOMPARE(spy2.count(), 1);
args = spy2.takeFirst();
QVERIFY2(args.empty(), "DataCleared signal has parameters now?!?");
}
В этом примере для простоты я проверил 2 сигнала PanelConcat
в одном тесте, но было бы лучше держать эти вещи изолированными в двух разных тестах. Помните об этом при написании реальных юнит-тестов.
Исходный код
Полный исходный код этого руководства доступен на GitHub.
Полная структура проекта включает 3 подпроекта:
WidgetsLib
– динамическая библиотека, содержащая класс виджета;ExampleApp
– пример приложения, использующего виджетPanelConcat
;TestPanelConcat
– юнит-тестPanelConcat
.
Чтобы попробовать этот пример в работе, загрузите в Qt Creator верхний проект с поддиректориями под названием GuiUnitTestingAdv
.
Имейте в виду, что по умолчанию при запуске проекта запускается пример приложения. Для запуска модульных тестов вы можете изменить активную конфигурацию запуска, использовать панель Тесты или использовать меню Инструменты (Tools) → Тесты (Tests) → Запустить все (Run All Tests).
Справочная информация
Чтобы узнать больше о Qt Test, вы можете ознакомиться с последней документацией по пространству имен QTest
и классу QSignalSpy
.
Заключение
Написание модульных тестов для графического интерфейса пользователя обычно сложнее, чем для «традиционного» кода бэкэнда/библиотеки. Это потому, что вы не можете просто протестировать общедоступные функции, и потому что между элементами существует более сильная связь. Тем не менее, как вы можете видеть из этого и предыдущего постов, с помощью Qt Test можно смоделировать практически все виды взаимодействия с пользователем.