Pull to refresh

Разработка для Sailfish OS: Модульное тестирование Qt/C++ под Sailfish OS

Reading time 5 min
Views 4.6K
Здравствуйте! Эта статья является продолжением цикла статей о тестировании Sailfish-приложений (предыдущая статья), и на этот раз мы рассмотрим модульное тестирование С++ в рамках проектов для Sailfish OS.

Тестируемое приложение


Итак, у нас имеется элементарный пример приложения под Sailfish OS, который доступен в репозитории данного проекта (о том, как создать приложение для Sailfish OS можно прочитать в одной из предыдущих статей). QML составляющая содержит единственный экран с приветствием.

import QtQuick 2.0
import Sailfish.Silica 1.0

ApplicationWindow 
{
  Label  
  { 
    x: Theme.horizontalPageMargin 
    text: "Hello Sailors" 
    color: Theme.secondaryHighlightColor 
    font.pixelSize: Theme.fontSizeExtraLarge 
  }
}

Кроме этого имеется один небольшой класс на языке C++:
class MyClass {
public:
    MyClass();
    MyClass(int first, int second);
    int add() const;
    int multiply() const;
private:
    int firstValue, secondValue;
};

Смысл его прост – он хранит 2 значения и находит их сумму и произведение. Этот класс мы и будем тестировать.

Создание проекта


Тесты и само приложение необходимо разместить в качестве поддиректорий одного проекта. Для этого можно при создании нового проекта выбрать пункт «Проект с поддиректориями»:


Либо в уже созданном *.pro файле указать TEMPLATE = subdirs. Этот шаблон указывает, что проект содержит в себе поддиректории. Теперь в контекстном меню основного проекта имеется возможность добавить подпроекты:


Таких подпроектов нужно хотя бы два: один их них будет являться самим приложением, второй — тестами к нему. При создании с помощью QtCreator, они автоматически добавится в *.pro файл в качестве поддиректории. Если приложение уже создано, его название можно просто добавить в переменную SUBDIRS в *.pro вручную. В итоге SUBDIRS будет выглядеть примерно так:
SUBDIRS = \
app \ 
tests

Перейдем к созданию самих тестов. Для модульного тестирования в Qt применяется фреймворк QtTest, который уже упоминался в предыдущей статье. Чтобы его использовать, нужно в *.yaml файле проекта в зависимости при сборке с помощью PkgConfig добавить Qt5Test.
Для создания тестов, в *.pro файле подпроекта, содержащего набор тестов следует подключить модуль testlib:
QT += testlib

Также, нужно указать файлы, которые будут тестироваться. Самый простой способ сделать это — создать *.pri файл в поддиректории с проектом, и в нем указать путь к тестируемым классам:
HEADERS += $$PWD/src/myclass.h 
SOURCES += $$PWD/src/myclass.cpp 

Далее его нужно включить в *.pro файлы приложения и проекта с тестами:
INCLUDEPATH += ../app/
include(../app/app.pri)

В TARGET указывается название подпроекта. Позже, файл с таким же именем необходимо будет запустить для выполнения тестов.

После этого, *.pro файл тестового подпроекта будет выглядеть примерно следующим образом:
TARGET = SailfishProjectTest

CONFIG += sailfishapp qt c++11

QT += testlib

HEADERS += testmyclass.h

SOURCES += testmyclass.cpp \
nain.cpp

INCLUDEPATH += ../app/
include(../app/app.pri)

Написание тестов


Для написания тестов реализуется отдельный класс, содержащий тестовые сценарии. Он должен быть наследником класса QObject. Сами тесты добавляются в виде приватных слотов этого класса. Каждый из слотов будет выступать в качестве тестовой функции.

Необходимо отметить, что в библиотеке QtTest присутствуют методы, позволяющие выполнить настройку данных для тестов перед их выполнением, а также прибраться после выполнения тестов:
  • initTestCase() — вызывается перед первой тестовой функцией, если в нем возникнет ошибка, ни одна тестовая функция не будет выполнена.
  • cleanupTestCase() — вызывается после выполнения всех тестовых функций.
  • init() — вызывается перед каждой тестовой функцией, если в нем возникнет ошибка, последующий тест не будет выполнен.
  • cleanup() — вызывается после каждой тестовой функции.

Применив описанную выше информацию, можно получить примерно такой класс, отвечающий за тестирования нашего проекта:
#include <QObject>
#include "src/myclass.h"

class TestMyClass : public QObject {
    Q_OBJECT
private:
    MyClass myClass;
private slots:
    void init();
    void testAdd();
    void testMultiply();
};

Для сравнения результатов выполнения функции с ожидаемым результатом используются макроподстановки:
  • QVERIFY(condition) в качестве аргумента принимает выражение и, в случае его ошибочности, выводит стандартное сообщение об ошибке в журнал тестирования.
  • QVERIFY2(condition, message) аналогично QVERIFY(), но выводит указанное в аргументах сообщение, если условие ложно.
  • QTRY_VERIFY_WITH_TIMEOUT(condition, timeout) схожа с QVERIFY(), но повторяет сравнение, пока условие не окажется верным или пока не истечет время, указанное во втором аргументе.
  • QTRY_VERIFY2_WITH_TIMEOUT(condition, message, timeout) схожа с QVERIFY2(), повторяет сравнение так же, как и QTRY_VERIFY_WITH_TIMEOUT().
  • QTRY_VERIFY(condition), QTRY_VERIFY2(condition, message) аналогичны описанным выше, но с таймером в 5 секунд.
  • QCOMPARE(actual, expected) дает более подробную информацию о провалившемся тесте. В качестве аргументов передаются результат выполнения функции и ожидаемый результат, если они не совпадают, то оба этих значения отображаются в журнале тестирование.
  • QTRY_COMPARE_WITH_TIMEOUT(actual, expected, timeout) аналогично QCOMPARE(), но повторяет сравнение, пока значения не окажется верным, или пока не достигнуто указанное время в миллисекундах.
  • QTRY_COMPARE(actual, expected) то же, что и QTRY_COMPARE_WITH_TIMEOUT(), но с таймером в 5 секунд.

Больше информации о макросах можно найти в документации по QTest.

Воспользуемся информацией выше для написания наших тестовых функций:
#include <QtTest/QtTest>
#include "src/myclass.h"
#include "testmyclass.h"

void TestMyClass::init() {
    myClass =  MyClass(4, 2);
}

void TestMyClass::testAdd() {
    QCOMPARE(myClass.add(), 6);
}

void TestMyClass::testMultiply() {
    QCOMPARE(myClass.multiply(), 8);
}

Так как в нашем проекте тестирующий класс разделен на .h и .cpp файлы, то на этом шаге процесс написание модульных тестов завершается. Однако, если .h файл отсутствует, а весь класс полностью описан в .cpp файле, то нужно подключить автоматически генерируемый .moc файл. Например, #include "testmyclass.moc".

Последнее, что остается сделать, организовать точку входа для запуска тестов. Для этого в *.cpp файле класса с тестами, либо в отдельном main.cpp используется один из трех макросов: QTEST_MAIN()/QTEST_APPLESS_MAIN()/QTEST_GUILESS_MAIN(). В качестве аргумента им передается название тестового класса. Каждый из макросов объявляет функцию main(), поэтому может быть использовано только один раз в подпроекте. Разные классов с юнит-тестами следует располагать в отдельных подпроектах.

Запуск тестов


Итак, проект готов, запускаем его из среды. После успешного запуска на устройстве в директории /usr/bin появится файл с название, указанным в TARGET. Просто выполняем данный файл.
*********Start testing of TestMyClass *********
Config: Using QtTest library 5.2.2 Qt 5.2.2
PASS    : TestMyClass::initTestCase()
PASS    : TestMyClass::testAdd()
PASS    : TestMyClass::testMultiply()
PASS    : TestMyClass::cleanupTestCase()
Totals: 4 passed, 0 failed, 0 skipped
********Finish testing of TestMyClass *********

Заключение


В данной статье был рассмотрен способ написания модульных тестов для тестирования приложений для платформы Sailfish OS. В качестве примера было рассмотрено простое приложение, исходники которого (вместе с тестами) доступны на GitHub.

Технические вопросы можно также обсудить на канале русскоязычного сообщества Sailfish OS в Telegram или группе ВКонтакте.

Автор: Максим Костерин
Tags:
Hubs:
+6
Comments 0
Comments Leave a comment

Articles