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

我刚刚开始一个需要一些跨平台GUI的新项目,我们选择了Qt作为GUI框架。 我们也需要一个单元测试框架。直到大约一年前,我们使用了内部开发的C ++单元测试框架 - 项目,但我们现在正在转向使用Google Test进行新项目。 有没有人有使用Google Test for Qt-applications的经验? QtTest / QTestLib是更好的选择吗? 我仍然不确定我们想在项目的非GUI部分使用Qt多少 - 我们可能更喜欢在核心代码中使用STL / Boost,并使用基于Qt的GUI的小接口。 编辑:看起来很多人都倾向于QtTest。有没有人有经验将这个与连续集成服务器集成?此外,在我看来,必须为每个新测试用例处理单独的应用程序会导致很多摩擦。有什么好方法可以解决这个问题吗? Qt Creator是否有一个处理此类测试用例的好方法,或者您是否需要为每个测试用例创建一个项目?     
已邀请:
我不知道QTestLib在一般术语中比另一个框架“更好”。有一件事做得很好,这提供了一种测试基于Qt的应用程序的好方法。 您可以将QTest集成到基于Google Test的新设置中。我没有尝试过,但根据QTestLib的架构,看起来它不会太复杂。 使用纯QTestLib编写的测试具有可以使用的-xml选项,以及一些XSLT转换,可转换为持续集成服务器所需的格式。但是,很多情况取决于您使用的CI服务器。我想这同样适用于GTest。 每个测试用例的单个测试应用程序从来没有给我带来太大的摩擦,但这取决于拥有一个构建系统,可以在管理测试用例的构建和执行方面做得不错。 我不知道Qt Creator中的每个测试用例需要一个单独的项目,但是自从我上次查看Qt Creator以来它可能已经改变了。 我还建议坚持使用QtCore并远离STL。在整个过程中使用QtCore将使处理需要Qt数据类型的GUI位更容易。在这种情况下,您不必担心从一种数据类型转换为另一种数据类型。     
您不必创建单独的测试应用程序。只需在与此类似的独立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文档中并没有很好地描述,即使它对许多人来说似乎非常有用。     
附加到Joe的回答。 这是我使用的一个小头(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;
}
    
我开始使用QtTest为我的应用程序,非常,很快就开始遇到它的限制。两个主要问题是: 1)我的测试运行得非常快 - 足够快,加载可执行文件,设置Q(核心)应用程序(如果需要)等的开销经常使测试本身的运行时间相形见绌!链接每个可执行文件也需要花费很多时间。 随着越来越多的类被添加,开销不断增加,很快就成了一个问题 - 单元测试的目标之一就是拥有一个安全网,运行速度快,根本不是负担,这是迅速变得不是这样。解决方案是将多个测试套件集成到一个可执行文件中,虽然(如上所示)这是可行的,但它不受支持且具有重要的局限性。 2)没有夹具支持 - 对我而言是一个交易破坏者。 所以过了一段时间,我转而使用Google Test - 它是一个功能更强大,更复杂的单元测试框架(特别是与Google Mock一起使用时)并解决1)和2),而且,你仍然可以轻松使用方便的QTestLib功能例如QSignalSpy和GUI事件的模拟等。切换有点痛苦,但幸运的是项目没有进展太多,许多变化都可以自动化。 就个人而言,我不会在未来的项目中使用QtTest而不是谷歌测试 - 如果没有提供我能看到的真正优势,并且有重要的缺点。     
为什么不使用Qt中包含的单元测试框架? 一个例子:QtTestLib教程。     
QtTest主要用于测试需要Qt事件循环/信号调度的部件。它的设计方式是每个测试用例都需要一个单独的可执行文件,因此它不应该与用于其余应用程序的任何现有测试框架冲突。 (顺便说一句,我强烈建议使用QtCore,即使是非GUI部分的应用程序。使用起来要好得多。)     
为了扩展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):tt" << qAppName();
         qDebug().noquote() << "print all test classes:tttt" << 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;
}
    
我使用gtest和QSignalSpy对我们的库进行了单元测试。使用QSignalSpy捕获信号。您可以直接调用插槽(如常规方法)来测试它们。     
如果您使用Qt,我建议使用QtTest,因为它具有测试UI的功能,并且易于使用。 如果你使用QtCore,你可能没有STL。我经常发现Qt类比STL类更容易使用。     
我一直在玩这个。使用Google Test而不是QtTest的主要优点是我们在Visual Studio中进行所有UI开发。如果您使用Visual Studio 2012并安装Google Test Adapter,则可以让VS识别测试并将其包含在Test Explorer中。这对于开发人员在编写代码时能够使用非常有用,并且因为Google Test是可移植的,所以我们也可以将测试添加到Linux构建的末尾。 我希望将来有人会将C ++的支持添加到C#所拥有的并发测试工具之中,比如NCrunch,Giles和ContinuousTests。 当然,您可能会发现有人为VS2012编写了另一个适配器,它为测试适配器添加了QtTest支持,在这种情况下,这种优势就消失了!如果有人对此感兴趣,那么有一篇很好的博客文章创作一个新的Visual Studio单元测试适配器。     
对于使用QtTest框架的Visual Studio测试适配器工具支持,请使用此Visual Studio扩展:https://visualstudiogallery.msdn.microsoft.com/cc1fcd27-4e58-4663-951f-fb02d9ff3653     

要回复问题请先登录注册