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

Добавлено 4 января 2021 в 22:33

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

Модульное тестирование графического интерфейса пользователя с помощью Qt Test

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

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

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

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

В этом руководстве будет обсуждаться тестирование класса QWidget под названием PanelConcat, который представляет центральный виджет следующего окна:

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

Виджет содержит 2 поля ввода и 2 кнопки. Нажатие кнопки CONCAT объединяет строки двух полей ввода и выводит результат на виджет надписи QLabel. Нажатие кнопки CANCEL удаляет все видимые данные. Ничего особенного или сложного, но это всего лишь пример.

Тестирование кода GUI обычно требует подхода, отличающегося от обычного модульного тестирования. Это потому, что обычно тестирования общедоступных функций виджета недостаточно. Чтобы правильно протестировать виджет, обычно лучше, если класс тестировщика может получить доступ ко всем внутренним данным и функциям. Самый простой способ добиться этого – сделать класс тестировщика другом тестируемого класса.

В данном примере, чтобы предоставить полный доступ TestPanelConcat, необходимо изменить PanelConcat следующим образом:

class WIDGETSLIBSHARED_EXPORT PanelConcat : public QWidget
{
  Q_OBJECT

  friend class TestPanelConcat;

public:

При тестировании сложных структур виджетов может потребоваться предоставить полный доступ более чем одному классу тестировщика. В этом случае возможным решением является создание класса друзей-посетителей, который будет использоваться всеми тестировщиками.

Базовое тестирование графического интерфейса

Простейший тест, который можно выполнить с виджетом, – это проверка правильности создания всех его элементов.

void TestPanelConcat::TestConstruction()
{
	QVERIFY2(panel.mInputA, "Input field A not created");
	QVERIFY2(panel.mInputB, "Input field B not created");

	QVERIFY2(panel.mLabelRes, "Result label not created");
}

В этом случае проверка довольно проста, поскольку код просто проверяет, не являются ли указатели различных внутренних виджетов NULL. В реальном приложении всё может быть сложнее, так как некоторые элементы создаются не всегда, и поэтому при модульном тестировании важно охватить все случаи.

Еще одно базовое тестирование графического интерфейса – проверка правильности установки всех важных свойств виджета и его элементов.

void TestPanelConcat::TestSize()
{
	QVERIFY2(panel.minimumWidth() == PanelConcat::MIN_W, "Minimum width not set.");
	QVERIFY2(panel.minimumHeight() == PanelConcat::MIN_H, "Minimum height not set");
}

В этом примере TestSize проверяет, установлен ли минимальный размер панели.

Обратите внимание, что в этом случае я не использовал QCOMPARE, потому что он не работает со статической константой, определенной в другом классе. Возможное решение этой проблемы – присвоить внешнюю статическую константу локальной константе и передать ее QCOMPARE, но в этом случае я решил упростить задачу и использовал QVERIFY.

Тестирование использования графического интерфейса

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

Qt Test предлагает несколько функций для программной отправки событий клавиатуры или мыши в QWidget.

Код в этом разделе будет имитировать следующие действия:

  1. запись в 2 поля ввода;
  2. нажатие кнопки CONCAT для получения результата;
  3. нажатие кнопки CANCEL для очистки всех данных.

Ожидаемый результат этого теста – увидеть в конце пустые поля ввода и виджет надписи.

Первый шаг выполняется с помощью функции QTest::keyClicks, которая имитирует последовательность нажатий клавиш на виджете:

void TestPanelConcat::TestClear()
{
	// запись в поля ввода
	QTest::keyClicks(panel.mInputA, STR1);
	QTest::keyClicks(panel.mInputB, STR2);

Последовательность клавиш кодируется в QString. Например, строка "www" означает, что виджет получит 3 нажатия клавиши w.

Второй шаг выполняется с помощью функции QTest::mouseClick, которая имитирует клик мышкой по виджету:

	// клик по кнопке CONCAT
	QTest::mouseClick(panel.mButtonConcat, Qt::LeftButton);
	// клик по кнопке CANCEL
	QTest::mouseClick(panel.mButtonCancel, Qt::LeftButton);

По умолчанию QTest::mouseClick имитирует клик в середине виджета, что нормально для кнопки, но также можно указать и другую позицию.

Последний шаг – просто убедиться, что весь текст в виджете пуст.

	// проверить, все ли поля пусты
	QVERIFY2(panel.mInputA->text().isEmpty(), "Input A not empty");
	QVERIFY2(panel.mInputB->text().isEmpty(), "Input B not empty");
	QVERIFY2(panel.mLabelRes->text().isEmpty(), "Label result not empty");
}

Тестирование на основе данных

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

Чтобы предоставить данные тестовой функции, вы должны создать еще один частный слот с тем же именем функции, но с дополнительным суффиксом "_data". Например, функцией данных для TestConcat() является TestConcat_data().

Реализация функции данных немного похожа на вставку данных в базу данных. Сначала вы определяете свои данные, как если бы вы определяли таблицу:

void TestPanelConcat::TestConcat_data()
{
	QTest::addColumn<QTestEventList>("inputA");
	QTest::addColumn<QTestEventList>("inputB");
	QTest::addColumn<QString>("result");

Тип первых двух «столбцов» – QTestEventList, который представляет собой список событий клавиатуры или мыши.

В этом случае я сохраняю 2 строки, содержащие нажатия клавиш в обоих списках

	QTestEventList listA;
	QTestEventList listB;

	// -- Normal A + B --
	listA.addKeyClicks(STR1);
	listB.addKeyClicks(STR2);

В конце в набор данных добавляется строка данных:

	QTest::newRow("Normal A + B") << listA << listB << STR_RES;

	// ... еще данные ...
}

Каждая строка содержит имя и список значений. Вы можете представить, что предыдущий код преобразован во что-то вроде следующей таблицы:

ИндексИмяinputAinputBresult
0"Normal A + B"listAlistBSTR_RES

После того, как мы определили функцию данных, мы можем написать тестовую функцию, которая разделена на 3 части.

Первая часть извлекает данные и строку с помощью макроса QFETCH:

void TestPanelConcat::TestConcat()
{
	QFETCH(QTestEventList, inputA);
	QFETCH(QTestEventList, inputB);
	QFETCH(QString, result);

Затем эти данные используются для имитации взаимодействия с виджетом. В частности, функция QTestEventList::simulate имитирует на целевом виджете одно за другим события из списка событий:

	// запись в поля ввода
	inputA.simulate(panel.mInputA);
	inputB.simulate(panel.mInputB);

	// клик по кнопке CONCAT
	QTest::mouseClick(panel.mButtonConcat, Qt::LeftButton);

Наконец, последняя часть проверяет, что произошло:

	// сравнение результата
	QCOMPARE(panel.mLabelRes->text(), result);
}

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

Без подхода, основанного на данных, нам пришлось бы многократно повторять в коде эти 3 шага.

Исходный код

Полный исходный код данного руководства доступен на GitHub.

Полная структура проекта включает 3 подпроекта:

  • WidgetsLib – динамическая библиотека, содержащая класс виджета;
  • ExampleApp – пример приложения, использующего виджет PanelConcat;
  • TestPanelConcat – юнит-тест PanelConcat.

Чтобы попробовать этот пример в работе, загрузите в Qt Creator верхний проект с поддиректориями под названием GuiUnitTestingIntro.

Имейте в виду, что по умолчанию при запуске проекта запускается пример приложения. Для запуска модульных тестов вы можете изменить активную конфигурацию запуска, использовать панель Тесты или использовать меню Инструменты (Tools) → Тесты (Tests) → Запустить все (Run All Tests).

Справочная информация

Чтобы узнать больше о Qt Test, вы можете ознакомиться с последней документацией по пространству имен QTest.

Заключение

Как было показано в данном руководстве, модульное тестирование графического интерфейса пользователя с помощью Qt Test - это реальная возможность, и разработчики на Qt должны учитывать ее при работе с кодом. Тестирование всех возможных взаимодействий с пользовательским интерфейсом, конечно, нетривиальная задача, но покрытие большей части определенно возможно, и Qt Test предлагает достаточную поддержку для этого.

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

Теги

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

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

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