Модульное тестирование GUI (графического интерфейса пользователя) с помощью Qt Test. Часть 2. Расширенное тестирование

Добавлено 5 января 2021 в 16:27

В данном руководстве рассматриваются более сложные темы модульного тестирования графического интерфейса пользователя с помощью Qt Test, фреймворка Qt для модульного тестирования кода на C++. Обсуждается и подробно анализируется рабочий пример. Предоставляется полный проект qmake и исходный код на C++.

Больше модульного тестирования GUI с помощью Qt Test

В данном руководстве будут представлены более продвинутые функции Qt Test, посвященные модульному тестированию графического интерфейса. В частности, будет показано, как имитировать и обрабатывать фокус клавиатуры и как тестировать сигналы Qt при модульном тестировании GUI.

Это четвертый и последний пост из серии, посвященной Qt Test. Посты этой серии:

Чтобы полностью понять новые концепции, которые я представлю здесь, рекомендую прочитать предыдущие посты этой серии, в частности первую часть модульного тестирования графического интерфейса с помощью Qt Test, .

Тестирование QWidget

В этом руководстве будет продолжено тестирование QWidget, представленного в части 1:

Рисунок 1 Пример приложения Qt для модульного тестирования GUI
Рисунок 1 – Пример приложения Qt для модульного тестирования GUI

Виджет содержит 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 Моделирование фокуса GUI
Рисунок 2 – Моделирование фокуса GUI

Следующий код выполняет запись в 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 можно смоделировать практически все виды взаимодействия с пользователем.

Теги

C++ / CppGUI / Графический интерфейс пользователяQtQtTestQWidgetМодульное тестирование / Юнит-тестирование / Unit testing

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.