Тестирование с помощью модуля Qt QTestLib

Я начал писать несколько тестов с модульной системой тестирования Qt.

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

Я хочу написать тесты для модуля. Модуль предоставляет только один класс, который будет использоваться пользователем модуля, но есть много логики, абстрагированной в других классах, которые я также хотел бы протестировать, помимо тестирования публичного класса.

Проблема заключается в том, что Qt, предлагаемый для запуска тестов, включал макрос QTEST_MAIN:

QTEST_MAIN(TestClass)
#include "test_class.moc"

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

Конечно, можно взглянуть на макрос QTEST_MAIN, переписать его и запустить другие тестовые классы. Но есть ли что-то, что работает из коробки?

До сих пор я делаю это вручную:

#include "one.h"
#include "two.h"

int main(int argc, char *argv[]) 
{ 
    QCoreApplication app(argc, argv); 
    TestOne one;
    QTest::qExec(&one, argc, argv);
    TestOne two;
    QTest::qExec(&two, argc, argv);
}

Ответ 1

Да, QTest заставляет бить странную тестовую структуру и, как правило, уступает Google Test/Mock Framework. Для одного проекта я вынужден использовать QTest (требование клиента), и вот как я его использую:

  • Я собираю все тесты вместе как проект шаблона subdir.
  • Чтобы упростить создание новых тестов, я разделяю много конфигурации проекта, используя файл common.pri, который я включаю в каждый тестовый файл .pro.
  • Если возможно, я разделяю каталог объектных файлов, чтобы ускорить компиляцию.
  • Я запускаю их все, используя пакетный + awk + sed script.

Настройка этих четырех точек очень проста и делает использование QTest практически приятным. У вас есть некоторые проблемы с запуском нескольких тестов, которые не решены с помощью описанной выше конфигурации?

PS: выполняется тестирование так, как вы это делаете, т.е. вызов нескольких QTest:: qExec вызывает проблемы с -o командной строки - вы получите только результаты для последнего тестируемого класса.

Ответ 2

В нашей установке с QTest мы сделали несколько вещей, чтобы сделать ее более приятной.

  • Определите подкласс QObject, который используется как базовый класс для любого нового класса unit-test.
  • В конструкторе для этого класса мы добавляем экземпляр теста в статический список тестов, а в деструкторе мы его удаляем.
  • Затем у нас есть статическая функция, которая проходит тесты и запускает их с помощью QTest::qExec(). (Мы накапливаем возвращаемые значения каждый раз и возвращаем это из нашей функции.)
  • main() вызывает эту функцию и возвращает результат как успех/сбой.
  • Наконец, в блоке компиляции самого конкретного теста мы обычно включаем статический экземпляр этого класса.

Эта настройка означает, что класс будет создан до запуска main(), поэтому он будет добавлен в список классов для тестирования при выполнении основных запусков. Рамки требуют, чтобы вам просто нужно наследовать свой класс правильно и создать экземпляр статического экземпляра, если вы всегда хотите его запустить.

Мы также иногда создаем другие необязательные тесты, которые добавляются на основе ключей командной строки.

Ответ 3

Относительно ответа, отправленного @cjhuitt

Это пример, который устраняет необходимость ручного вызова каждого тестового объекта

Я ПЫТАЯ ИЗБЕЖАТЬ ВЕЩЕЙ, КАК ЭТО:

MyTestClass1 t1;   t1.run();
MyTestClass2 t2;   t2.run();
//etc...

Мое решение - позволить тестовым объектам наследовать от базового класса, который добавляет себя в статический список Затем основная программа выполняет все тестовые объекты в этом списке. Таким образом, ни один из поддерживающего кода рамки не нуждается в изменении. Единственное, что меняется, это сами классы тестов.

Вот как я это делаю:

qtestsuite.h - базовый класс для тестовых объектов

#ifndef QTESTSUITE_H
#define QTESTSUITE_H

#include <QObject>
#include <vector>

class QTestSuite : public QObject
{
    Q_OBJECT
public:
    static std::vector<QObject*> m_suites;

public:
    explicit QTestSuite();

};

#endif // QTESTSUITE_H

qtestsuite.cpp

#include "qtestsuite.h"
#include <iostream>

std::vector<QObject*> QTestSuite::m_suites;

QTestSuite::QTestSuite() : QObject()
{
    m_suites.push_back(this);
}

testall.cpp - запускает тесты

#include "qtestsuite.h"

#include <QtTest/QtTest>
#include <iostream>

int main(int, char**)
{
    int failedSuitesCount = 0;
    std::vector<QObject*>::iterator iSuite;
    for (iSuite = QTestSuite::m_suites.begin(); iSuite != QTestSuite::m_suites.end(); iSuite++)
    {
        int result = QTest::qExec(*iSuite);
        if (result != 0)
        {
            failedSuitesCount++;
        }
    }
    return failedSuitesCount;
}

mytestsuite1.cpp - пример тестового объекта, создайте больше этих

#include "qtestsuite.h"

#include <QtTest/QtTest>

class MyTestSuite1: public QTestSuite
{
     Q_OBJECT
private slots:
    void aTestFunction();
    void anotherTestFunction();
};

void MyTestSuite1::aTestFunction()
{
    QString str = "Hello";
    QVERIFY(str.toUpper() == "this will fail");
}

void MyTestSuite1::anotherTestFunction()
{
    QString str = "Goodbye";
    QVERIFY(str.toUpper() == "GOODBYE");
}

static MyTestSuite1 instance;  //This is where this particular test is instantiated, and thus added to the static list of test suites

#include "mytestsuite1.moc"

также, чтобы создать файл .pro

qmake -project "CONFIG += qtestlib"

Ответ 4

Обычно я организую тесты с одним тестовым исполняемым для каждого тестируемого класса.

и, в конце концов, одна тестовая программа способный тестировать только один тест класс.

Это хорошо. Он изолирует ваши тесты друг от друга, предотвращая такие ситуации, как крах в одном тесте, от блокировки всех ваших других тестов. Эта авария может быть вызвана общим компонентом в нескольких тестируемых классах. Образ неудач будет затем отбросить вас на исходное происхождение проблемы. В принципе, у вас есть лучшая диагностическая информация о сбоях, если ваши тесты независимы друг от друга.

Упростите настройку нескольких исполняемых файлов и выполните каждый тест отдельно. Используйте тестовый бегун, чтобы отбросить все тестовые процессы.

Update:

Я немного изменил свое мнение. Когда у вас есть большая программа с большим количеством тестов, связывание сотен тестовых исполняемых файлов становится очень медленным. Мое новое предпочтение состоит в том, чтобы поместить все тесты для библиотеки в исполняемый файл и выбрать, какие тесты вызывать с использованием аргументов командной строки, переданных в тестовый исполняемый файл.

Это сокращает количество исполняемых файлов от сотен до десятков, но сохраняет преимущества выполнения тестов отдельно.