Подтвердить что ты не робот

Какую единицу тестирования я должен использовать для Qt?

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

Нам также нужна инфраструктура модульного тестирования. До года назад мы использовали встроенную платформу для тестирования модулей для С++ - проектов, но теперь мы переходим к использованию Google Test для новых проектов.

Есть ли у кого-нибудь опыт использования Google Test для Qt-приложений? Является ли QtTest/QTestLib лучшей альтернативой?

Я все еще не уверен, сколько мы хотим использовать Qt в частях, не относящихся к GUI проекта, - мы, вероятно, предпочли бы просто использовать STL/Boost в основном коде с небольшим интерфейсом к графическому интерфейсу на основе Qt.

EDIT: Похоже, что многие склоняются к QtTest. Есть ли кто-нибудь, кто имеет опыт интеграции с непрерывным сервером интеграции? Кроме того, мне показалось, что необходимость обработки отдельного приложения для каждого нового тестового примера вызовет много трений. Есть ли хороший способ решить это? У Qt Creator есть хороший способ обработки таких тестовых случаев или вам нужен проект на тестовый случай?

4b9b3361

Ответ 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.