Модульное тестирование кода на C++ с Qt Test. Часть 1. Введение

Добавлено 2 января 2020 в 20:35

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

Модульное тестирование кода на C++ с помощью Qt Test

Qt Test – это среда для модульного тестирования кода на C++. Он является частью Qt, что означает, что он включает в себя функции для тестирования графического интерфейса и других элементов Qt, таких как сигналы, но его также можно использовать для тестирования простого (без Qt) кода на C++.

В данном руководстве я собираюсь объяснить, как использовать Qt Test для тестирования класса на C++. Это потребует создания проекта, определения модульного теста и использования различных макросов, доступных для тестирования кода.

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

Настройка проекта

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

Самый быстрый способ создать проект – использовать шаблон «Проект автотестирования» (Auto Test Project), который находится в группе «Другой проект» (Other Project) диалогового окна «Новый проект» (New Project).

Рисунок 1 Создание проекта автоматического тестирования в Qt Creator для модульного тестирования кода на C++ с помощью Qt Test
Рисунок 1 – Создание проекта автоматического тестирования в Qt Creator для модульного тестирования кода на C++ с помощью Qt Test

Мастер проведет вас через настройку проекта. В частности, в разделе «Подробнее» (Details) можно указать несколько параметров.

Рисунок 2 Детали настройки проекта автотестирования в Qt Creator
Рисунок 2 – Детали настройки проекта автотестирования в Qt Creator

«Имя теста» (Test case name) будет именем класса, представляющего юнит-тест.

Если вы не хотите использовать мастер проекта, вам нужно добавить testlib в переменную QT в qmake файле проекта:

QT += testlib

Если в вашем модульном тесте нет элементов графического интерфейса, можно отключить модуль gui:

QT -= gui

Написание модульного теста

Чтобы написать модульный тест кода на C++ с Qt, вам необходимо создать класс, который наследуется от QObject и реализует, по крайней мере, один частный слот. Каждый частный слот – это независимый тест.

Самый простой класс модульного теста выглядит примерно так:

#include <QtTest>

class TestMinimal : public QObject
{
    Q_OBJECT

private slots:
    void testFoo();
};

Вы также можете реализовать 4 специальных частных слота, которые используются для операций инициализации и очистки:

// вызывается перед первой тестовой функцией
void initTestCase();
// вызывается перед каждой тестовой функцией
void init();
// вызывается после каждой тестовой функции
void cleanup();
// вызывается после последней тестовой функции
void cleanupTestCase();

Кроме того, у вас в тестовом классе могут быть и другие члены данных и функции, поскольку это обычный класс C++.

Пример класса модульного теста

Для этого руководства я написал простой пример класса для тестирования с именем Calculator. Этот класс выполняет основные математические операции с 2 целыми числами. Ничего особенного, в конце концов, это всего лишь пример.

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

Класс модульного теста:

#include "Calculator.h"

#include <QtTest/QtTest>

class TestCalculator: public QObject
{
    Q_OBJECT

private slots:
    // -- настройка/очистка --
    void init();

    // -- тесты --
    void testConstructor();
    void testSum();

private:
    const int A0 = 0;
    const int B0 = 0;

private:
    Calculator mCalc;
};

Функции класса TestCalculator будут объяснены в следующих разделах.

Проверка значений

Для проверки того, что во время теста всё работает так, как ожидалось, Qt Test предоставляет различные макросы.

Самая простая проверка, которую вы можете выполнить, – это проверить, истинно ли утверждение. Для этого вы можете использовать макрос QVERIFY:

void TestCalculator::testConstructor()
{
    // значения по умолчанию
    Calculator c1;

    QVERIFY(c1.getA() == 0);
    QVERIFY(c1.getB() == 0);

В этом коде всё хорошо, пока функции getA() и getB() возвращают 0. Если это так, при запуске теста вы увидите следующее сообщение:

PASS : TestCalculator::testConstructor()

В случае сбоя сообщение уведомит вас о том, что что-то пошло не так:

FAIL! : TestCalculator::testConstructor() 'c1.getA() == 0' returned FALSE. ()
   Loc: [../QtTestIntroduction/TestCalculator.cpp(16)]

Сообщение об ошибке не содержит подробной информации о том, что пошло не так. Оно только говорит вам, какое выражение было ложным.

Если вам нужно добавить к проверке немного контекста, вы можете использовать вторую версию макроса, QVERIFY2, который позволяет добавить сообщение об ошибке:

    // полный конструктор
    const int A = 10;
    const int B = 2;
    Calculator c2(A, B);

    QVERIFY2(c2.getA() == A, "first operand doesn't match");  // не совпадает первый операнд
    QVERIFY2(c2.getB() == B, "second operand doesn't match"); // не совпадает второй операнд
}

В случае сбоя сообщение отладки добавит дополнительную информацию, переданную в макрос:

FAIL! : TestCalculator::testConstructor() 'c2.getB() == B' returned FALSE. (second operand doesn't match)
 Loc: [../QtTestIntroduction/TestCalculator.cpp(25)]

Добавление сообщения об ошибке может показаться лишней работой, но оно поможет людям, не знакомым с вашим кодом. Вы всегда должны предпочесть использование QVERIFY2 (вместо QVERIFY), чтобы включать в ваши тесты дополнительную информацию.

Qt Test также предоставляет другие версии QVERIFY, которые предоставляют некоторые дополнительные функции. Например, QTRY_VERIFY_WITH_TIMEOUT или QVERIFY_EXCEPTION_THROWN. Первый вариант позволяет проверять условие несколько раз, прежде чем посчитать его ложным. Последний проверяет, выброшено ли конкретное исключение. Для получения дополнительной информации обратитесь к справочной информации в конце данного руководства.

Сравнение значений

Если вам нужна дополнительная информация, когда тест не проходит, и если вы хотите сравнить какое-либо значение, вам понадобится макрос QCOMPARE:

void TestCalculator::testSum()
{
    // sum default
    QCOMPARE(mCalc.Sum(), A0 + B0);

Этот макрос будет сравнивать 2 параметра с использованием наиболее подходящего оператора тестирования. Например, для сравнения чисел с плавающей запятой используется функция Qt qFuzzyCompare().

Когда QCOMPARE выдает сбой, он предоставляет более подробную информацию:

FAIL! : TestCalculator::testSum() Compared values are not the same
Actual (mCalc.Sum()): 10
Expected (A0 + B0) : 12
 Loc: [../QtTestIntroduction/TestCalculator.cpp(39)]

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

Также QCOMPARE имеет разные версии, предлагающие дополнительные функции, например QTRY_COMPARE_WITH_TIMEOUT, который проверяет условие несколько раз, прежде чем считать его ложным. Для получения дополнительной информации обратитесь к справочной информации в конце статьи.

Функция main приложения

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

Для формирования кода функции main, в соответствии с вашими потребностями, Qt Test предоставляет 3 макроса:

// полное приложение Qt
QTEST_MAIN(TestName)

// приложение Qt только с core: без GUI, но есть обработка событий
QTEST_GUILESS_MAIN(TestName)

// нет приложения Qt: без GUI и обработки событий
QTEST_APPLESS_MAIN(TestName)

Вы можете добавить один из этих макросов в конец файла cpp, определяющего юнит-тест. В этом примере, поскольку я тестировал простой код на C++ (без Qt), я использовал:

QTEST_APPLESS_MAIN(TestCalculator)

Следует помнить, что если вы объявляете класс юнит-теста непосредственно в файле .cpp (а не в файле .h), вам нужно будет добавить в конце дополнительный #include:

QTEST_APPLESS_MAIN(TestCalculator)
#include "TestCalculator.moc"

Это требует Qt для правильной работы.

Исходный код

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

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

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

Заключение

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

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

Теги

C++ / CppqmakeQtQt CreatorQtTestМодульное тестирование / Юнит-тестирование / Unit testing

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

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