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

Я начал писать некоторые тесты с системой модульного тестирования 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);
}

4 ответа

Решение

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

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

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

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

Относится к ответу, опубликованному @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"

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

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

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

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

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

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

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

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

Обновить:

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

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

Другие вопросы по тегам