Pytanie Jakie ramy testowania jednostkowego należy użyć dla Qt? [Zamknięte]


Właśnie rozpoczynam nowy projekt, który wymaga kilkuinterfejsowego GUI, a my wybieramy Qt jako framework GUI.

Potrzebne są także ramy testów jednostkowych. Do około roku temu korzystaliśmy z własnej opracowanej struktury testowania jednostkowego dla projektów C ++, ale obecnie przechodzimy do korzystania z Google Test w przypadku nowych projektów.

Czy ktokolwiek ma doświadczenie w korzystaniu z Google Test dla aplikacji Qt? Czy QtTest / QTestLib jest lepszą alternatywą?

Nadal nie jestem pewien, jak bardzo chcemy używać Qt w częściach projektu innych niż GUI - prawdopodobnie wolelibyśmy po prostu użyć STL / Boost w kodzie źródłowym z małym interfejsem do GUI opartego na Qt.

EDYTOWAĆ: Wygląda na to, że wielu skłania się ku QtTest. Czy jest ktoś, kto ma jakiekolwiek doświadczenie z integracją tego z ciągłym serwerem integracyjnym? Wydaje mi się również, że posiadanie oddzielnej aplikacji dla każdego nowego przypadku testowego spowodowałoby duże tarcie. Czy istnieje jakiś dobry sposób na rozwiązanie tego problemu? Czy program Qt Creator ma dobry sposób obsługi takich przypadków testowych, czy też potrzebujesz projektu w każdym przypadku testowym?


44
2017-10-06 08:54


pochodzenie




Odpowiedzi:


Nie wiem, że QTestLib jest "lepszy" niż jeden framework dla drugiego na takich ogólnych warunkach. Jest jedna rzecz, która dobrze się sprawdza i jest to dobry sposób testowania aplikacji opartych na Qt.

Możesz zintegrować QTest z nową konfiguracją opartą na Testach Google. Nie próbowałem tego, ale na podstawie tego, jak QTestLib został zaprojektowany, wydaje się, że nie byłoby to zbyt skomplikowane.

Testy napisane przy użyciu czystej QTestLib mają opcję -xml, której możesz użyć, wraz z transformacjami XSLT, aby przekonwertować na wymagany format dla ciągłego serwera integracyjnego. Jednak wiele z tego zależy od tego, z którym serwerem CI korzystasz. Wyobrażam sobie, że to samo dotyczy GTest.

Pojedyncza aplikacja testowa na przypadek testowy nigdy nie powodowała dużego tarcia, ale to zależy od posiadania systemu kompilacji, który wykonałby przyzwoitą robotę zarządzania budowaniem i wykonaniem przypadków testowych.

Nie znam niczego w Qt Creator, który wymagałby osobnego projektu w każdym przypadku testowym, ale mógł się zmienić od czasu, gdy ostatnio patrzyłem na Qt Creator.

Sugerowałbym także trzymanie się QtCore i trzymanie się z daleka od STL. Korzystanie z QtCore przez cały czas ułatwi obsługę bitów GUI wymagających typów danych Qt. W takim przypadku nie musisz się martwić o konwersję z jednego typu danych na inny.


17
2017-10-06 17:42



Chciałbym tylko zwrócić uwagę na potomność, która zmieniła się znacznie w ciągu 8 lat od opublikowania pierwotnego pytania. W 2017 r. Wybór googletest / mock na QTestLib wydaje się być bardziej realnym wyborem niż poprzednio. Jako Załącznik A, jest to seminarium internetowe od ICS Qt Rozwój oparty na testach przy użyciu Google Test i Google Mock. Dla mnie używanie googletest / mock razem z QSignalSpy był naprawdę skuteczny, nie widzę żadnego powodu, by kiedykolwiek wrócić. - evadeflow


Nie musisz tworzyć oddzielnych aplikacji testowych. Po prostu użyj qExec w niezależnej funkcji main () podobnej do tej:

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

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

    // ...

    return 0;
}

Spowoduje to wykonanie wszystkich metod testowych w każdej klasie w jednej partii.

Twoje pliki testclass .h wyglądałyby następująco:

class TestClass1 : public QObject
{
Q_OBJECT

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

Niestety, ta konfiguracja nie jest tak dobrze opisana w dokumentacji Qt, mimo że wydaje się być całkiem użyteczna dla wielu osób.


37
2017-09-27 14:51



Podoba mi się to podejście, ale dostaję błąd Unknown test function: 'test()'. Possible matches: firstTest(), jakaś wskazówka? - Purefan
qExec nie powinien być wywoływany więcej niż jeden raz: ponieważ łamie obsługę linii poleceń. Podczas pisania testu za pomocą QtTest możesz wyświetlić wszystkie funkcje testu za pomocą -functions i uruchom pojedynczą funkcję, przekazując ją w wierszu poleceń. Dzieje się tak, gdy qExec jest wywoływane więcej niż jeden raz, ponieważ tylko pierwsze wywołanie obsługuje opcje wiersza poleceń. - Aurélien Gâteau


Aby dołączyć do odpowiedzi Joe.

Oto mały nagłówek, którego używam (testrunner.h), zawierający klasę narzędzi spawning pętlę zdarzeń (która jest na przykład potrzebna do przetestowania oczekujących połączeń i baz danych) i "działających" klas kompatybilnych z 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

Użyj tego tak:

#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
2017-10-02 00:44



dobra robota! ponownie powinno być QCoreApplication w prywatnym automacie ... - relascope
Dzięki, naprawione! Nie testowałem jeszcze, ale wkrótce (jak używam odmiany tego kodu, gdy potrzebuję testów :)) - mlvljr
To jest naprawdę świetne! - Terrabits
... Jedno pytanie: czy istnieje jakiś powód, dla którego użyłeś? QTimer zamiast QMetaObject::invokeMethod z Qt::QueuedConnection? Myślę, że byłoby to bardziej czytelne IMHO. - Terrabits
@ Terrabity Po co umieszczać połączenia, jeśli obiekt będzie miał powinowactwo do tego samego wątku co QTimer? (Który powinien być wątkiem GUI, ponieważ tworzenie qApp-ów na zewnątrz jest prawdopodobnie zabronione)? :) - mlvljr


Zacząłem używać QtTest do mojej aplikacji i bardzo szybko zacząłem z nią korzystać. Dwa główne problemy to:

1) Moje testy przebiegają bardzo szybko - na tyle szybko, że narzut ładowania pliku wykonywalnego, ustawienie aplikacji Q (Core) (w razie potrzeby) itp. Często skraca czas wykonywania samych testów! Łączenie każdego pliku wykonywalnego zajmuje również dużo czasu.

Koszty ogólne stale rosły, gdy dodawano coraz więcej zajęć, a wkrótce stało się to problemem - jednym z celów testów jednostkowych jest posiadanie sieci bezpieczeństwa, która działa tak szybko, że nie jest to wcale obciążenie, a to było szybko się nie dzieje. Rozwiązaniem jest globalizacja wielu zestawów testów w jednym pliku wykonywalnym, a jednocześnie (jak pokazano powyżej) jest to w większości możliwe do zrealizowania Nieobsługiwany i ma ważne ograniczenia.

2) Brak wsparcia dla fixture - dla mnie przełom.

Po pewnym czasie przełączyłem się na Test Google - jest to znacznie bardziej funkcjonalny i wyrafinowany framework do testowania jednostkowego (szczególnie w przypadku korzystania z Google Mock) i rozwiązuje 1) i 2), a ponadto nadal można w łatwy sposób korzystać z wygodnych funkcji QTestLib takie jak QSignalSpy i symulacja zdarzeń GUI itd. Zmiana ustawień była nieco trudna, ale na szczęście projekt nie posunął się zbyt daleko i wiele zmian można zautomatyzować.

Osobiście nie będę korzystał z testu QtTest nad Google Test dla przyszłych projektów - jeśli oferty nie dają żadnych rzeczywistych korzyści, które widzę i mają ważne wady.


18
2017-10-02 08:54



Jakąkolwiek radę jak połączyć Qt i gtest? IE: czy nadal masz QApplication lub QMainWindow? Czy umieścisz swoje testy bezpośrednio w głównej lub w funkcji członka jakiegoś potomka QObject? - KeyserSoze
@KeyserSoze Wszelkie testy end-to-end / integracyjne wymagające QWidget mają QApplication; to QApplication jest tworzone (ale nie exec () "d) w main (), przed RUN_ALL_TESTS (). Można użyć QMainWindow, ale używam go głównie w testach end-to-end. Same testy są zgodne ze standardowym schematem testów Google i generalnie mam np. wszystkie testy jednostkowe dla klasy X w pliku o nazwie Xtests.cpp. Jest to zasadniczo standardowy projekt gtest, z kilkoma ustępstwami na Qt (tworzenie aplikacji QApplication przed uruchomieniem testów w normalny sposób). - SSJ_GZ
Nie wiem, czy to ostatnie, ale Qt Test pozwala na dodanie dwóch prywatnych automatów w tym i sprzątać które są wywoływane odpowiednio przed i po każdej funkcji testowej. - markand


Dlaczego nie skorzystać z frameworka do testowania jednostkowego zawartego w Qt? Przykład : Samouczek QtTestLib.


7
2017-10-06 08:57



"Czy QtTest / QTestLib jest lepszą alternatywą?" ... Myślę, że to jest pytanie :-P - Terrabits


Test QtTest jest głównie przydatny do testowania części, które wymagają pętli zdarzeń / wysyłania sygnałów Qt. Został zaprojektowany w taki sposób, że każdy przypadek testowy wymaga osobnego pliku wykonywalnego, więc nie powinien kolidować z istniejącym środowiskiem testowym używanym w pozostałej części aplikacji.

(Btw, bardzo polecam używanie QtCore nawet dla części aplikacji nieobsługujących GUI.)


3
2017-10-06 08:59





Aby rozszerzyć rozwiązanie mlvljr i Joe's, możemy nawet obsługiwać kompletne opcje QtTest na jedną klasę testową i nadal działać w trybie wsadowym plus rejestrowanie:

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

nagłówek

#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

własny kod

#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
2017-07-03 11:22





I jednostka przetestowała nasze biblioteki przy użyciu gtest i QSignalSpy. Użyj QSignalSpy do przechwytywania sygnałów. Możesz wywoływać gniazda bezpośrednio (jak normalne metody), aby je przetestować.


3
2018-03-16 09:45





Jeśli używasz Qt, polecam używanie QtTest, ponieważ ma on możliwości testowania interfejsu użytkownika i jest prosty w użyciu.

Jeśli używasz QtCore, prawdopodobnie możesz zrobić bez STL. Często uważam, że klasy Qt są łatwiejsze w użyciu niż odpowiedniki STL.


2
2017-10-06 09:51