Какую единицу тестирования я должен использовать для Qt?
Я только начинаю новый проект, для которого нужен кросс-платформенный графический интерфейс, и мы выбрали Qt как графический интерфейс.
Нам также нужна инфраструктура модульного тестирования. До года назад мы использовали встроенную платформу для тестирования модулей для С++ - проектов, но теперь мы переходим к использованию Google Test для новых проектов.
Есть ли у кого-нибудь опыт использования Google Test для Qt-приложений? Является ли QtTest/QTestLib лучшей альтернативой?
Я все еще не уверен, сколько мы хотим использовать Qt в частях, не относящихся к GUI проекта, - мы, вероятно, предпочли бы просто использовать STL/Boost в основном коде с небольшим интерфейсом к графическому интерфейсу на основе Qt.
EDIT: Похоже, что многие склоняются к QtTest. Есть ли кто-нибудь, кто имеет опыт интеграции с непрерывным сервером интеграции? Кроме того, мне показалось, что необходимость обработки отдельного приложения для каждого нового тестового примера вызовет много трений. Есть ли хороший способ решить это? У Qt Creator есть хороший способ обработки таких тестовых случаев или вам нужен проект на тестовый случай?
Ответы
Ответ 1
Я не знаю, что QTestLib "лучше", чем одна структура для другого в таких общих терминах. Есть одна вещь, что он делает хорошо, и это хороший способ тестирования приложений на основе Qt.
Вы можете интегрировать QTest в новую настройку на основе Google Test. Я не пробовал, но, основываясь на том, как QTestLib архивируется, кажется, что это не будет слишком сложно.
В тестах, написанных с помощью чистого QTestLib, есть опция -xml, которую вы можете использовать вместе с некоторыми преобразованиями XSLT для преобразования в необходимый формат для сервера непрерывной интеграции. Однако многое зависит от того, с каким сервером CI вы работаете. Я бы предположил, что это относится и к GTest.
Одно тестовое приложение на тестовый случай никогда не вызывало большого трения для меня, но это зависит от наличия системы сборки, которая могла бы выполнить достойную работу по управлению зданием и выполнением тестовых случаев.
Я ничего не знаю в Qt Creator, который потребует отдельного проекта для каждого тестового примера, но он может измениться с тех пор, как я последний раз смотрел на Qt Creator.
Я также предлагаю придерживаться QtCore и держаться подальше от STL. Использование QtCore повсюду будет иметь дело с битами GUI, которые требуют типов данных Qt. В этом случае вам не придется беспокоиться о переходе от одного типа данных к другому.
Ответ 2
Вам не нужно создавать отдельные тестовые приложения. Просто используйте 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, хотя она, по-видимому, очень полезна для многих людей.
Ответ 3
Чтобы добавить к Джо ответ.
Вот небольшой заголовок, который я использую (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;
}
Ответ 4
Я начал использовать QtTest для своего приложения и очень быстро начал сталкиваться с ограничениями. Две основные проблемы:
1) Мои тесты выполняются очень быстро - достаточно быстро, что накладные расходы на загрузку исполняемого файла, настройка приложения Q (Core) (при необходимости) и т.д. часто затмевают время работы самих тестов! Связывание каждого исполняемого файла занимает много времени.
Накладные расходы просто увеличивались по мере того, как добавлялось все больше и больше классов, и вскоре это стало проблемой - одна из целей модульных тестов - иметь защитную сетку, которая работает так быстро, что это не бремя, и это быстро стало не так. Решение состоит в том, чтобы объединить несколько наборов тестов в один исполняемый файл, а в то время как (как показано выше) это в основном не работает, не поддерживается и имеет важные ограничения.
2) Отсутствие поддержки крепежа - нарушитель сделки для меня.
Итак, через некоторое время я переключился на Google Test - это гораздо более функциональная и сложная модульная система тестирования (особенно при использовании с Google Mock) и решает 1) и 2), и, кроме того, вы все еще можете легко использовать удобные функции QTestLib, такие как QSignalSpy и симуляция графических интерфейсов и т.д. Было немного больно переключиться, но, к счастью, проект не продвинулся слишком далеко, и многие из этих изменений могут быть автоматизированы.
Лично я не буду использовать QtTest над Google Test для будущих проектов - если не предлагает реальных преимуществ, которые я вижу, и имеет важные недостатки.
Ответ 5
Почему бы не использовать инфраструктуру модульного тестирования, включенную в Qt?
Пример: Учебное пособие QtTestLib.
Ответ 6
QtTest в основном полезен для тестирования частей, требующих диспетчеризации цикла Qt. Он разработан таким образом, что для каждого тестового примера требуется отдельный исполняемый файл, поэтому он не должен конфликтовать с существующей тестовой средой, используемой для остальной части приложения.
(Кстати, я настоятельно рекомендую использовать QtCore даже для сторонних приложений, отличных от GUI, с которыми гораздо удобнее работать.)
Ответ 7
Чтобы расширить решение 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;
}
Ответ 8
Если вы используете Qt, я бы рекомендовал использовать QtTest, потому что у него есть возможности для тестирования пользовательского интерфейса и прост в использовании.
Если вы используете QtCore, вы, вероятно, можете обойтись без STL. Я часто нахожу классы Qt более легкими в использовании, чем аналоги STL.
Ответ 9
Я тестировал наши библиотеки с помощью gtest и QSignalSpy. Используйте QSignalSpy для обнаружения сигналов. Вы можете напрямую вызывать слоты (например, обычные методы).
Ответ 10
Я просто играл с этим. Главное преимущество использования Google Test для QtTest для нас в том, что мы делаем все наши разработки пользовательского интерфейса в Visual Studio. Если вы используете Visual Studio 2012 и установите Google Test Adapter, вы можете заставить VS распознать тесты и включить их в свой тестовый проводник. Это отлично подходит разработчикам для использования при написании кода, а поскольку Google Test переносим, мы также можем добавить тесты в конец нашей сборки Linux.
В будущем я надеюсь, что кто-то добавит поддержку С++ к одному из параллельных инструментов тестирования, которые имеют С#, например NCrunch, Giles и ContinuousTests.
Конечно, вы можете найти, что кто-то пишет другой адаптер для VS2012, который добавляет поддержку QtTest тестовому адаптеру, и в этом случае это преимущество уходит! Если кто-то заинтересован в этом, есть хорошая запись в блоге Создание нового адаптера Visual Studio unit test.
Ответ 11
Для поддержки инструмента адаптера тестовой адаптера Visual Studio с картой QtTest это расширение Visual Studio: https://visualstudiogallery.msdn.microsoft.com/cc1fcd27-4e58-4663-951f-fb02d9ff3653