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

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

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

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

проблема в том, что предлагаемый 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() вызывает эту функцию и возвращает результат как success/failure.
  • наконец, в блок компиляции самого конкретного теста мы обычно включаем статический экземпляр этого класса.

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

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


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

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

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

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

обновление:

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

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