web-dev-qa-db-fra.com

Quel framework de tests unitaires dois-je utiliser pour Qt?

Je commence tout juste un nouveau projet nécessitant une interface graphique multi-plateforme, et nous avons choisi Qt comme framework GUI.

Nous avons également besoin d'un cadre de test unitaire. Il y a environ un an, nous utilisions un framework de tests unitaires développé en interne pour les projets C++, mais nous passons maintenant à l'utilisation de Google Test pour les nouveaux projets.

Quelqu'un a-t-il déjà utilisé Google Test for Qt-applications? QtTest/QTestLib est-il une meilleure alternative?

Je ne sais toujours pas dans quelle mesure nous voulons utiliser Qt dans les parties non graphiques du projet - nous préférerions probablement simplement utiliser STL/Boost dans le code principal avec une petite interface avec l'interface graphique basée sur Qt.

EDIT: Il semble que beaucoup se tournent vers QtTest. Y a-t-il quelqu'un qui a une expérience dans l'intégration de cela avec un serveur d'intégration continue? De plus, il me semblerait que le fait de devoir traiter une application distincte pour chaque nouveau cas de test causerait beaucoup de friction. Y at-il un bon moyen de résoudre ce problème? Qt Creator dispose-t-il d'un bon moyen de gérer de tels tests ou auriez-vous besoin d'un projet par test?

45
Rasmus Faber

Je ne sais pas si QTestLib est "meilleur" qu'un framework pour un autre en termes aussi généraux. Il y a une chose que ça fait bien, et c'est un bon moyen de tester les applications basées sur Qt. 

Vous pouvez intégrer QTest dans votre nouvelle configuration basée sur Google Test. Je ne l'ai pas essayé, mais d'après l'architecture de QTestLib, il semble que ce ne serait pas trop compliqué.

Les tests écrits avec QTestLib pur ont une option -xml que vous pouvez utiliser, ainsi que des transformations XSLT pour la conversion au format requis pour un serveur d'intégration continue. Cependant, cela dépend en grande partie du serveur CI que vous utilisez. J'imagine que la même chose s'applique à GTest.

Une seule application de test par cas de test ne m'a jamais causé beaucoup de frictions, mais cela dépend d'un système de compilation qui permettrait de gérer correctement la création et l'exécution des cas de test.

Je ne connais rien dans Qt Creator qui exigerait un projet séparé par scénario de test, mais cela aurait pu changer depuis la dernière fois que j'ai consulté Qt Creator.

Je suggérerais également de rester avec QtCore et de rester à l'écart de la STL. L’utilisation de QtCore facilitera la gestion des bits de l’interface graphique qui requièrent les types de données Qt. Dans ce cas, vous n'aurez pas à vous soucier de la conversion d'un type de données en un autre.

18
mattr-

Vous n'êtes pas obligé de créer des applications de test séparées. Utilisez simplement qExec dans une fonction main () indépendante similaire à celle-ci:

int main(int argc, char *argv[])
{
    TestClass1 test1;
    QTest::qExec(&test1, argc, argv);

    TestClass2 test2;
    QTest::qExec(&test2, argc, argv);

    // ...

    return 0;
}

Ceci exécutera toutes les méthodes de test dans chaque classe en un seul lot.

Vos fichiers .cl de testclass ressemblent à ceci:

class TestClass1 : public QObject
{
Q_OBJECT

private slots:
    void testMethod1();
    // ...
}

Malheureusement, cette configuration n’est pas vraiment décrite dans la documentation de Qt, bien qu’elle semble très utile pour beaucoup de gens.

37
Joe

Joindre à la réponse de Joe. 

Voici un petit en-tête que j'utilise (testrunner.h), contenant une classe d'utilitaires générant une boucle d'événement (nécessaire, par exemple, pour tester les connexions de fentes de signal et les bases de données en file d'attente) et "en cours d'exécution":

#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

Utilisez-le comme ceci:

#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;
}
19
mlvljr

J'ai commencé à utiliser QtTest pour mon application et très rapidement, j'ai commencé à rencontrer des limitations avec cette application. Les deux problèmes principaux étaient:

1) Mes tests s'exécutent très rapidement - suffisamment rapidement pour que le temps de chargement d'un exécutable, la configuration d'une application Q (Core) (si nécessaire), etc. annule souvent la durée d'exécution des tests eux-mêmes! Lier chaque exécutable prend également beaucoup de temps.

Les frais généraux ne cessaient de croître à mesure que de plus en plus de classes étaient ajoutées, et cela devenait rapidement un problème - l'un des objectifs des tests unitaires est de disposer d'un filet de sécurité tellement rapide que ce n'est pas un fardeau du tout. devenir rapidement pas le cas. La solution consiste à regrouper plusieurs suites de tests dans un seul exécutable (bien que, comme indiqué ci-dessus, cette fonctionnalité soit principalement réalisable, elle est non prise en charge et présente des limitations importantes.

2) Pas de support de montage - un deal-breaker pour moi.

Ainsi, après un certain temps, je suis passé à Google Test. Il s'agit d'un framework de tests unitaires beaucoup plus complet et sophistiqué (en particulier lorsqu'il est utilisé avec Google Mock) et résout 1) et 2). De plus, vous pouvez toujours utiliser facilement les fonctionnalités pratiques de QTestLib. telles que QSignalSpy et la simulation d’événements d’interface graphique, etc. C’était un peu difficile de changer de système, mais heureusement, le projet n’avait pas progressé de manière considérable et de nombreux changements pouvaient être automatisés. 

Personnellement, je n'utiliserai pas QtTest sur Google Test pour de futurs projets - si cela ne présente aucun avantage réel à mon avis et présente des inconvénients importants.

19
SSJ_GZ

Pourquoi ne pas utiliser le framework de tests unitaires inclus dans Qt? Un exemple: Tutoriel QtTestLib .

7

Pour étendre les solutions de mlvljr et de Joe, nous pouvons même prendre en charge des options QtTest complètes pour une classe de test, tout en continuant à tout exécuter par lot, avec enregistrement:

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]]...

Entête

#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

propre code

#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;
}
3
j.holetzeck

J'ai un peu testé nos bibliothèques en utilisant gtest et QSignalSpy . Utilisez QSignalSpy pour capturer les signaux. Vous pouvez appeler directement les créneaux horaires (comme les méthodes normales) pour les tester.

3
BЈовић

QtTest est principalement utile pour tester des pièces nécessitant l'envoi de boucle d'événement/signal Qt. Il est conçu de manière à ce que chaque scénario de test nécessite un exécutable distinct. Il ne doit donc pas entrer en conflit avec les infrastructures de test existantes utilisées pour le reste de l'application.

(Au fait, je recommande fortement d'utiliser QtCore, même pour des parties non-graphiques de l'application. C'est beaucoup plus agréable de travailler avec.)

3

Si vous utilisez Qt, je vous recommanderais d'utiliser QtTest, car il dispose de fonctionnalités permettant de tester l'interface utilisateur et est simple à utiliser.

Si vous utilisez QtCore, vous pouvez probablement vous passer de STL. Je trouve souvent que les classes Qt sont plus faciles à utiliser que les homologues de la STL.

2
Gunther Piez

Je viens de jouer avec ça. Le principal avantage de l'utilisation de Google Test sur QtTest pour nous est que nous développons tous nos interfaces utilisateur dans Visual Studio. Si vous utilisez Visual Studio 2012 et installez le Google Test Adapter , vous pouvez faire en sorte que le VS reconnaisse les tests et les inclue dans son explorateur de tests. C’est formidable pour les développeurs de pouvoir utiliser lorsqu’ils écrivent du code, et comme Google Test est portable, nous pouvons également ajouter les tests à la fin de notre version de Linux.

J'espère qu'à l'avenir, quelqu'un ajoutera la prise en charge du C++ à l'un des outils de test simultanés proposés par C #, comme NCrunch , Giles et ContinuousTests .

Bien sûr, il est possible que quelqu'un trouve un autre adaptateur pour VS2012 qui ajoute le support QtTest à Test Adapter, auquel cas cet avantage disparaît! Si cela vous intéresse, voici un bon article de blog Création d'un nouvel adaptateur de test d'unité Visual studio .

1
parsley72

Pour la prise en charge de l'outil d'adaptateur de test Visual Studio avec l'infrastructure QtTest, utilisez cette extension Visual Studio: https://visualstudiogallery.msdn.Microsoft.com/cc1fcd27-4e58-4663-951f-fb02d9ff3653

0
user3395798