51

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

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

有没有人有使用 Google Test for Qt 应用程序的经验?QtTest/QTestLib 是更好的选择吗?

我仍然不确定我们想在项目的非 GUI 部分中使用多少 Qt——我们可能更愿意在核心代码中使用 STL/Boost,并带有一个与基于 Qt 的 GUI 的小接口。

编辑:看起来很多人倾向于 QtTest。有没有人有将它与持续集成服务器集成的经验?此外,在我看来,必须为每个新测试用例处理一个单独的应用程序会导致很多摩擦。有什么好的方法可以解决吗?Qt Creator 是否有处理此类测试用例的好方法,或者您是否需要为每个测试用例创建一个项目?

4

11 回答 11

38

您不必创建单独的测试应用程序。只需在与此类似的独立 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 文档中并没有得到很好的描述,尽管它似乎对很多人来说非常有用。

于 2010-09-27T14:51:43.340 回答
21

我开始在我的应用程序中使用 QtTest,并且很快就开始遇到限制。两个主要问题是:

1) 我的测试运行得非常快 - 加载可执行文件、设置 Q(Core)Application(如果需要)等的开销通常会使测试本身的运行时间相形见绌!链接每个可执行文件也会占用大量时间。

随着越来越多的类添加,开销不断增加,很快就变成了一个问题——单元测试的目标之一是拥有一个运行得如此之快的安全网,以至于它根本不是负担,这是迅速变得并非如此。解决方案是将多个测试套件集成到一个可执行文件中,虽然(如上所示)这在很大程度上是可行的,但它不受支持并且有重要的限制。

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

所以过了一段时间,我切换到了 Google Test——它是一个功能更丰富、更复杂的单元测试框架(尤其是与 Google Mock 一起使用时)并解决了 1)和 2),此外,您仍然可以轻松使用方便的 QTestLib 功能比如 QSignalSpy 和 GUI 事件的模拟等。切换起来有点痛苦,但幸运的是该项目没有进展得太远,许多更改都可以自动化。

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

于 2012-10-02T08:54:49.603 回答
19

附加到乔的答案。

这是我使用的一个小头文件 (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;
}
于 2012-10-02T00:44:48.230 回答
18

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

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

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

每个测试用例的单个测试应用程序从来没有给我造成太大的摩擦,但这取决于有一个构建系统可以很好地管理测试用例的构建和执行。

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

我还建议坚持使用 QtCore 并远离 STL。在整个过程中使用 QtCore 将使处理需要 Qt 数据类型的 GUI 位更容易。在这种情况下,您不必担心从一种数据类型转换为另一种数据类型。

于 2009-10-06T17:42:21.493 回答
6

为什么不使用 Qt 中包含的单元测试框架?一个例子:QtTestLib 教程

于 2009-10-06T08:57:10.003 回答
4

我使用 gtest 和QSignalSpy对我们的库进行了单元测试。使用 QSignalSpy 捕捉信号。您可以直接调用插槽(如普通方法)来测试它们。

于 2017-03-16T09:45:11.283 回答
3

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

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

于 2009-10-06T08:59:48.550 回答
3

为了扩展 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;
}
于 2015-07-03T11:22:01.680 回答
2

如果您使用的是 Qt,我建议您使用 QtTest,因为它具有测试 UI 的功能并且易于使用。

如果您使用 QtCore,您可能可以不用 STL。我经常发现 Qt 类比 STL 类更易于使用。

于 2009-10-06T09:51:30.977 回答
1

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

我希望将来有人会将对 C++ 的支持添加到 C# 拥有的并发测试工具之一中,例如NCrunchGilesContinuousTests

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

于 2013-02-13T02:44:10.447 回答
0

对于 QtTest 框架的 Visual Studio 测试适配器工具支持,请使用此 Visual Studio 扩展:https ://visualstudiogallery.msdn.microsoft.com/cc1fcd27-4e58-4663-951f-fb02d9ff3653

于 2015-11-06T19:44:09.200 回答