Тестирование с помощью модуля 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:
Я немного изменил свое мнение. Когда у вас есть большая программа с большим количеством тестов, связывание сотен тестовых исполняемых файлов становится очень медленным. Мое новое предпочтение состоит в том, чтобы поместить все тесты для библиотеки в исполняемый файл и выбрать, какие тесты вызывать с использованием аргументов командной строки, переданных в тестовый исполняемый файл.
Это сокращает количество исполняемых файлов от сотен до десятков, но сохраняет преимущества выполнения тестов отдельно.