代码之家  ›  专栏  ›  技术社区  ›  Rasmus Faber

我应该为Qt使用什么单元测试框架?[闭门]

  •  45
  • Rasmus Faber  · 技术社区  · 16 年前

    我刚刚开始一个新项目,需要一些跨平台的GUI,我们选择了Qt作为GUI框架。

    我们也需要一个单元测试框架。直到大约一年前,我们还在C++项目中使用内部开发的单元测试框架,但现在我们正在过渡到在新项目中使用GoogleTest。

    有没有人有过在Qt应用程序中使用GoogleTest的经验?qtest/QTestLib是更好的选择吗?

    我仍然不确定我们希望在项目的非GUI部分中使用Qt的程度-我们可能更希望在核心代码中使用STL/Boost,并为基于Qt的GUI提供一个小型接口。

    编辑: 看起来很多人都倾向于测试。有没有人有将其与持续集成服务器集成的经验?此外,在我看来,必须为每个新的测试用例处理单独的应用程序会造成很大的摩擦。有什么好办法解决这个问题吗?QtCreator是否有处理此类测试用例的好方法,或者您是否需要每个测试用例都有一个项目?

    11 回复  |  直到 14 年前
        1
  •  38
  •   Joe    15 年前

    您不必创建单独的测试应用程序。只需在一个独立的main()函数中使用qExec,类似于以下函数:

    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文档中并没有很好地描述这个设置,尽管它似乎对很多人都很有用。

        2
  •  20
  •   maxschlepzig    10 年前

    1) 我的测试运行得非常快——足够快,以至于加载可执行文件、设置Q(核心)应用程序(如果需要)等的开销常常会使测试本身的运行时间相形见绌!链接每个可执行文件也会占用很多时间。

    随着越来越多的类被添加,开销一直在增加,这很快就成了一个问题——单元测试的目标之一就是要有一个运行速度如此之快的安全网,它根本不是一个负担,而这一点很快就不再是问题了。解决方案是将多个测试套件集成到一个可执行文件中,虽然(如上所示)这是最可行的,但它确实可行 not supported 并且具有重要的局限性。

    2) 没有固定设备支持-对我来说是一个交易破坏者。

    因此,过了一段时间,我切换到了Google Test——它是一个功能更强大、更复杂的单元测试框架(特别是与Google Mock一起使用时),解决了1)和2),而且,您仍然可以轻松使用方便的QTestLib功能,如QSignalSpy和GUI事件模拟等。切换起来有点痛苦,但谢天谢地,该项目并没有进展太快,许多更改都可以自动化。

    就我个人而言,我不会在未来的项目中使用QtTest而不是Google Test——如果它没有我所看到的真正的优势,并且有重要的缺点的话。

        3
  •  19
  •   mlvljr    11 年前

    附在乔的答案后面。

    下面是我使用的一个小标题(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
  •  18
  •   mattr-    13 年前

    我不知道QTestLib在如此笼统的意义上比一个框架比另一个框架“更好”。有一件事它做得很好,那就是提供了一种测试基于Qt的应用程序的好方法。

    您可以将QTest集成到新的基于Google测试的设置中。我还没有尝试过,但基于QTestLib的架构,它似乎不会太复杂。

    使用纯QTestLib编写的测试有一个-xml选项,您可以使用该选项以及一些XSLT转换来转换为持续集成服务器所需的格式。然而,这在很大程度上取决于您使用的CI服务器。我想这同样适用于GTest。

    每个测试用例使用一个测试应用程序从来不会给我带来太多的摩擦,但这取决于是否有一个构建系统能够很好地管理测试用例的构建和执行。

    我不知道QtCreator中有什么东西需要每个测试用例都有一个单独的项目,但是自从我上次查看QtCreator以来,它可能已经改变了。

        5
  •  6
  •   Patrice Bernassola    4 年前

    为什么不使用Qt中包含的单元测试框架呢? QtTestLib Tutorial .

        6
  •  4
  •   BЈовић    9 年前

    QSignalSpy . 使用QSignalSpy捕捉信号。您可以直接调用slot(像普通方法一样)来测试它们。

        7
  •  3
  •   LukáÅ¡ Lalinský    16 年前

    QtTest主要用于测试需要Qt事件循环/信号调度的部件。它的设计方式是,每个测试用例都需要一个单独的可执行文件,因此它不应该与应用程序其余部分使用的任何现有测试框架冲突。

    (顺便说一句,我强烈建议在应用程序的非GUI部分使用QtCore。使用它会更好。)

        8
  •  3
  •   jhole    10 年前

    为了扩展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;
    }
    
        9
  •  2
  •   Gunther Piez    16 年前

    如果您使用的是Qt,我建议您使用qtest,因为它具有测试UI的功能,而且使用简单。

    如果您使用QtCore,则可能不需要STL。我经常发现Qt类比STL类更容易使用。

        10
  •  1
  •   parsley72    13 年前

    我只是在玩弄这个。对我们来说,使用GoogleTest而不是QtTest的主要优势是我们在VisualStudio中进行所有UI开发。如果使用Visual Studio 2012并安装 Google Test Adapter 您可以让VS识别测试并将其包含在其测试资源管理器中。这对于开发人员在编写代码时能够使用是非常好的,因为googletest是可移植的,所以我们还可以将测试添加到Linux构建的末尾。

    NCrunch Giles ContinuousTests .

    当然,您可能会发现有人为VS2012编写了另一个适配器,为测试适配器添加了QtTest支持,在这种情况下,这种优势就消失了!如果有人对此感兴趣,有一篇很好的博客文章 Authoring a new Visual studio unit test adapter .