Какой фреймворк модульного тестирования я должен использовать для Qt?[закрыто]
-
19-09-2019 - |
Вопрос
Я только начинаю новый проект, которому нужен некоторый кроссплатформенный графический интерфейс, и мы выбрали Qt в качестве GUI-фреймворка.
Нам тоже нужен фреймворк модульного тестирования.Примерно год назад мы использовали разработанную собственными силами платформу модульного тестирования для C ++-проектов, но сейчас мы переходим к использованию Google Test для новых проектов.
Есть ли у кого-нибудь опыт использования Google Test для Qt-приложений?Является ли QtTest / QTestLib лучшей альтернативой?
Я все еще не уверен, насколько мы хотим использовать Qt в частях проекта, отличных от GUI - мы, вероятно, предпочли бы просто использовать STL / Boost в коде ядра с небольшим интерфейсом к GUI на основе Qt.
Редактировать: Похоже, что многие склоняются к QtTest.Есть ли кто-нибудь, у кого есть какой-либо опыт интеграции этого с сервером непрерывной интеграции?Кроме того, мне кажется, что необходимость обрабатывать отдельное приложение для каждого нового тестового примера вызовет много трений.Есть ли какой-нибудь хороший способ решить эту проблему?Есть ли у Qt Creator хороший способ обработки таких тестовых примеров или вам нужно иметь проект для каждого тестового примера?
Решение
Я не знаю, что QTestLib «лучше», чем один фреймворк для другого в таких общих чертах.Есть одна вещь, с которой он справляется хорошо: это хороший способ тестирования приложений на основе Qt.
Вы можете интегрировать QTest в свою новую настройку на основе Google Test.Я не пробовал, но, судя по архитектуре QTestLib, это не будет слишком сложно.
Тесты, написанные с использованием чистого QTestLib, имеют параметр -xml, который вы можете использовать вместе с некоторыми преобразованиями XSLT для преобразования в необходимый формат для сервера непрерывной интеграции.Однако многое зависит от того, какой CI-сервер вы используете.Я предполагаю, что то же самое относится и к GTest.
Одно тестовое приложение для каждого тестового примера никогда не вызывало у меня особых проблем, но это зависит от наличия системы сборки, которая могла бы достойно управлять созданием и выполнением тестовых примеров.
Я не знаю ничего в Qt Creator, что потребовало бы отдельного проекта для каждого тестового примера, но это могло измениться с тех пор, как я в последний раз смотрел Qt Creator.
Я бы также предложил придерживаться QtCore и держаться подальше от STL.Использование QtCore упростит работу с элементами графического интерфейса, требующими типов данных Qt.В этом случае вам не придется беспокоиться о преобразовании из одного типа данных в другой.
Другие советы
Вам не нужно создавать отдельные тестовые приложения.Просто используйте qExec в независимой функции main(), похожей на эту:
int main(int argc, char *argv[])
{
TestClass1 test1;
QTest::qExec(&test1, argc, argv);
TestClass2 test2;
QTest::qExec(&test2, argc, argv);
// ...
return 0;
}
Это приведет к выполнению всех тестовых методов в каждом классе в одном пакете.
Ваши файлы testclass .h будут выглядеть следующим образом:
class TestClass1 : public QObject
{
Q_OBJECT
private slots:
void testMethod1();
// ...
}
К сожалению, эта настройка недостаточно подробно описана в документации Qt, хотя многим она может показаться весьма полезной.
Чтобы добавить к ответу Джо.
Вот небольшой заголовок, который я использую (testrunner.h), содержащий служебный класс, порождающий цикл событий (который, например, необходим для тестирования соединений сигнальных слотов и баз данных в очереди) и «запуск» QTest-совместимых классов:
#ifndef TESTRUNNER_H
#define TESTRUNNER_H
#include <QList>
#include <QTimer>
#include <QCoreApplication>
#include <QtTest>
class TestRunner: public QObject
{
Q_OBJECT
public:
TestRunner()
: m_overallResult(0)
{}
void addTest(QObject * test) {
test->setParent(this);
m_tests.append(test);
}
bool runTests() {
int argc =0;
char * argv[] = {0};
QCoreApplication app(argc, argv);
QTimer::singleShot(0, this, SLOT(run()) );
app.exec();
return m_overallResult == 0;
}
private slots:
void run() {
doRunTests();
QCoreApplication::instance()->quit();
}
private:
void doRunTests() {
foreach (QObject * test, m_tests) {
m_overallResult|= QTest::qExec(test);
}
}
QList<QObject *> m_tests;
int m_overallResult;
};
#endif // TESTRUNNER_H
Используйте это следующим образом:
#include "testrunner.h"
#include "..." // header for your QTest compatible class here
#include <QDebug>
int main() {
TestRunner testRunner;
testRunner.addTest(new ...()); //your QTest compatible class here
qDebug() << "Overall result: " << (testRunner.runTests()?"PASS":"FAIL");
return 0;
}
Я начал использовать QtTest для своего приложения и очень, очень быстро начал сталкиваться с его ограничениями.Двумя основными проблемами были:
1) Мои тесты выполняются очень быстро - достаточно быстро, поэтому затраты на загрузку исполняемого файла, настройку Q(Core)Application (при необходимости) и т. д. часто затмевают время выполнения самих тестов!Связывание каждого исполняемого файла также занимает много времени.
Накладные расходы продолжали увеличиваться по мере того, как добавлялось все больше и больше классов, и вскоре это стало проблемой: одна из целей модульных тестов — создать систему безопасности, которая работала бы так быстро, чтобы вообще не была обузой, и это было быстро становится не так.Решение состоит в том, чтобы объединить несколько наборов тестов в один исполняемый файл, и хотя (как показано выше) это в основном выполнимо, но не поддерживается и имеет важные ограничения.
2) Отсутствие поддержки приспособлений - для меня это препятствие.
Поэтому через некоторое время я переключился на Google Test — это гораздо более функциональная и сложная среда модульного тестирования (особенно при использовании с Google Mock), которая решает 1) и 2), и, более того, вы все еще можете легко использовать удобные функции QTestLib. такие как QSignalSpy и моделирование событий графического интерфейса и т. д.Переключаться было немного сложно, но, к счастью, проект не продвинулся слишком далеко, и многие изменения можно было автоматизировать.
Лично я не буду использовать QtTest вместо Google Test для будущих проектов - если он не предлагает реальных преимуществ, которые я вижу, и имеет важные недостатки.
Почему бы не использовать среду модульного тестирования, включенную в Qt?Пример : Учебное пособие по QtTestLib.
Я тестировал наши библиотеки с помощью gtest и QSignalSpy.Используйте QSignalSpy для перехвата сигналов.Вы можете вызывать слоты напрямую (как обычные методы), чтобы протестировать их.
QtTest в основном полезен для тестирования частей, требующих диспетчеризации цикла событий/сигналов Qt.Он спроектирован таким образом, что для каждого тестового примера требуется отдельный исполняемый файл, поэтому он не должен конфликтовать с какой-либо существующей платформой тестирования, используемой для остальной части приложения.
(Кстати, я настоятельно рекомендую использовать QtCore даже для частей приложений, не связанных с графическим интерфейсом.С ним гораздо приятнее работать.)
Чтобы расширить решение mlvljr и Joe, мы можем даже поддерживать полные параметры QtTest для одного тестового класса и при этом запускать все в пакетном режиме плюс ведение журнала:
usage:
help: "TestSuite.exe -help"
run all test classes (with logging): "TestSuite.exe"
print all test classes: "TestSuite.exe -classes"
run one test class with QtTest parameters: "TestSuite.exe testClass [options] [testfunctions[:testdata]]...
Заголовок
#ifndef TESTRUNNER_H
#define TESTRUNNER_H
#include <QList>
#include <QTimer>
#include <QCoreApplication>
#include <QtTest>
#include <QStringBuilder>
/*
Taken from https://stackoverflow.com/questions/1524390/what-unit-testing-framework-should-i-use-for-qt
BEWARE: there are some concerns doing so, see https://bugreports.qt.io/browse/QTBUG-23067
*/
class TestRunner : public QObject
{
Q_OBJECT
public:
TestRunner() : m_overallResult(0)
{
QDir dir;
if (!dir.exists(mTestLogFolder))
{
if (!dir.mkdir(mTestLogFolder))
qFatal("Cannot create folder %s", mTestLogFolder);
}
}
void addTest(QObject * test)
{
test->setParent(this);
m_tests.append(test);
}
bool runTests(int argc, char * argv[])
{
QCoreApplication app(argc, argv);
QTimer::singleShot(0, this, SLOT(run()));
app.exec();
return m_overallResult == 0;
}
private slots:
void run()
{
doRunTests();
QCoreApplication::instance()->quit();
}
private:
void doRunTests()
{
// BEWARE: we assume either no command line parameters or evaluate first parameter ourselves
// usage:
// help: "TestSuite.exe -help"
// run all test classes (with logging): "TestSuite.exe"
// print all test classes: "TestSuite.exe -classes"
// run one test class with QtTest parameters: "TestSuite.exe testClass [options] [testfunctions[:testdata]]...
if (QCoreApplication::arguments().size() > 1 && QCoreApplication::arguments()[1] == "-help")
{
qDebug() << "Usage:";
qDebug().noquote() << "run all test classes (with logging):\t\t" << qAppName();
qDebug().noquote() << "print all test classes:\t\t\t\t" << qAppName() << "-classes";
qDebug().noquote() << "run one test class with QtTest parameters:\t" << qAppName() << "testClass [options][testfunctions[:testdata]]...";
qDebug().noquote() << "get more help for running one test class:\t" << qAppName() << "testClass -help";
exit(0);
}
foreach(QObject * test, m_tests)
{
QStringList arguments;
QString testName = test->metaObject()->className();
if (QCoreApplication::arguments().size() > 1)
{
if (QCoreApplication::arguments()[1] == "-classes")
{
// only print test classes
qDebug().noquote() << testName;
continue;
}
else
if (QCoreApplication::arguments()[1] != testName)
{
continue;
}
else
{
arguments = QCoreApplication::arguments();
arguments.removeAt(1);
}
}
else
{
arguments.append(QCoreApplication::arguments()[0]);
// log to console
arguments.append("-o"); arguments.append("-,txt");
// log to file as TXT
arguments.append("-o"); arguments.append(mTestLogFolder % "/" % testName % ".log,txt");
// log to file as XML
arguments.append("-o"); arguments.append(mTestLogFolder % "/" % testName % ".xml,xunitxml");
}
m_overallResult |= QTest::qExec(test, arguments);
}
}
QList<QObject *> m_tests;
int m_overallResult;
const QString mTestLogFolder = "testLogs";
};
#endif // TESTRUNNER_H
собственный код
#include "testrunner.h"
#include "test1"
...
#include <QDebug>
int main(int argc, char * argv[])
{
TestRunner testRunner;
//your QTest compatible class here
testRunner.addTest(new Test1);
testRunner.addTest(new Test2);
...
bool pass = testRunner.runTests(argc, argv);
qDebug() << "Overall result: " << (pass ? "PASS" : "FAIL");
return pass?0:1;
}
Если вы используете Qt, я бы рекомендовал использовать QtTest, поскольку он имеет возможности для тестирования пользовательского интерфейса и прост в использовании.
Если вы используете QtCore, то, вероятно, сможете обойтись без STL.Я часто нахожу классы Qt более простыми в использовании, чем их аналоги в STL.
Я просто поиграл с этим.Главное преимущество использования Google Test перед QtTest для нас заключается в том, что мы разрабатываем весь пользовательский интерфейс в Visual Studio.Если вы используете Visual Studio 2012 и устанавливаете Тестовый адаптер Google вы можете заставить VS распознавать тесты и включать их в свой обозреватель тестов.Это здорово, когда разработчики могут использовать его при написании кода, а поскольку Google Test переносим, мы также можем добавить тесты в конец нашей сборки Linux.
Я надеюсь, что в будущем кто-нибудь добавит поддержку C ++ в один из инструментов параллельного тестирования, которые есть в C #, например NCrunch ( Хруст ), Джайлс и Непрерывные тесты.
Конечно, вы можете обнаружить, что кто-то пишет другой адаптер для VS2012, который добавляет поддержку QtTest для Test Adapter, и в этом случае это преимущество исчезает!Если кому-то это интересно, есть хороший пост в блоге Создание нового адаптера модульного тестирования Visual Studio.
Для поддержки инструмента адаптера тестирования Visual Studio с платформой QtTest используйте это расширение Visual Studio: https://visualstudiogallery.msdn.microsoft.com/cc1fcd27-4e58-4663-951f-fb02d9ff3653