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

发布于 2024-08-07 06:49:35 字数 1539 浏览 14 评论 0原文

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

几度春秋 2024-08-14 06:49:35

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

You don't have to create separate tests applications. Just use qExec in an independent main() function similar to this one:

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

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

    // ...

    return 0;
}

This will execute all test methods in each class in one batch.

Your testclass .h files would look as follows:

class TestClass1 : public QObject
{
Q_OBJECT

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

Unfortunately this setup isn't really described well in the Qt documentation even though it would seem to be quite useful for a lot of people.

零崎曲识 2024-08-14 06:49:35

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

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

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

2)没有固定装置支持——对我来说这是一个破坏因素。

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

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

I started off using QtTest for my app and very, very quickly started running into limitations with it. The two main problems were:

1) My tests run very fast - sufficiently quickly that the overhead of loading an executable, setting up a Q(Core)Application (if needed) etc often dwarfs the running time of the tests themselves! Linking each executable takes up a lot of time, too.

The overhead just kept on increasing as more and more classes were added, and it soon became a problem - one of the goals of unit tests are to have a safety net that runs so fast that it is not a burden at all, and this was rapidly becoming not the case. The solution is to glob multiple test suites into one executable, and while (as shown above) this is mostly do-able, it is not supported and has important limitations.

2) No fixture support - a deal-breaker for me.

So after a while, I switched to Google Test - it is a far more featureful and sophisticated unit testing framework (especially when used with Google Mock) and solves 1) and 2), and moreover, you can still easily use the handy QTestLib features such as QSignalSpy and simulation of GUI events, etc. It was a bit of a pain to switch, but thankfully the project had not advanced too far and many of the changes could be automated.

Personally, I will not be using QtTest over Google Test for future projects - if offers no real advantages that I can see, and has important drawbacks.

撞了怀 2024-08-14 06:49:35

附加到乔的答案。

这是我使用的一个小标头(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;
}

To append to Joe's answer.

Here's a small header I use (testrunner.h), containing an utility class spawning an event loop (which is, for example, needed to test queued signal-slot connections and databases) and "running" QTest-compatible classes:

#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

Use it like this:

#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;
}
吹泡泡o 2024-08-14 06:49:35

从一般意义上来说,我不知道 QTestLib 比一个框架“更好”于另一个框架。它做得很好,那就是提供了一种测试基于 Qt 的应用程序的好方法。

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

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

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

我不知道 Qt Creator 中的任何内容需要每个测试用例一个单独的项目,但自上次我查看 Qt Creator 以来它可能已经发生了变化。

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

I don't know that QTestLib is "better" than one framework for another in such general terms. There is one thing that it does well, and that's provide a good way to test Qt based applications.

You could integrate QTest into your new Google Test based setup. I haven't tried it, but based on how QTestLib is architected, it seems like it would not be too complicated.

Tests written with pure QTestLib have an -xml option that you could use, along with some XSLT transformations to convert to the needed format for a continuous integration server. However, a lot of that depends on which CI server you go with. I would imagine the same applies to GTest.

A single test app per test case never caused a lot of friction for me, but that depends on having a build system that would do a decent job of managing the building and execution of the test cases.

I don't know of anything in Qt Creator that would require a seperate project per test case but it could have changed since the last time I looked at Qt Creator.

I would also suggest sticking with QtCore and staying away from the STL. Using QtCore throughout will make dealing with the GUI bits that require the Qt data types easier. You won't have to worry about converting from one data type to another in that case.

音盲 2024-08-14 06:49:35

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

Why not using the unit-testing framework included in Qt?
An example : QtTestLib Tutorial.

才能让你更想念 2024-08-14 06:49:35

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

I unit tested our libraries using gtest and QSignalSpy. Use QSignalSpy to catch signals. You can call slots directly (like normal methods) to test them.

路还长,别太狂 2024-08-14 06:49:35

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

(顺便说一句,即使对于应用程序的非 GUI 部分,我也强烈建议使用 QtCore。使用起来要好得多。)

QtTest is mostly useful for testing parts that require the Qt event loop/signal dispatching. It's designed in a way that each test case requires a separate executable, so it should not conflict with any existing test framework used for the rest of the application.

(Btw, I highly recommend using QtCore even for non-GUI parts of the applications. It's much nicer to work with.)

小霸王臭丫头 2024-08-14 06:49:35

为了扩展 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;
}

To extend mlvljr's and Joe's solution we can even support complete QtTest options per one test class and still run all in a batch plus logging:

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

Header

#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

own 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;
}
心凉 2024-08-14 06:49:35

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

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

If you are using Qt, I would recommend using QtTest, because is has facilities to test the UI and is simple to use.

If you use QtCore, you can probably do without STL. I frequently find the Qt classes easier to use than the STL counterparts.

只等公子 2024-08-14 06:49:35

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

我希望将来有人会将对 C++ 的支持添加到 C# 拥有的并发测试工具之一中,例如 NCrunch< /a>、Giles连续测试

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

I've just been playing around with this. The main advantage of using Google Test over QtTest for us is that we do all our UI development in Visual Studio. If you use Visual Studio 2012 and install the Google Test Adapter you can get VS to recognise the tests and include them in its Test Explorer. This is great for developers to be able to use as they write code, and because Google Test is portable we can also add the tests to the end of our Linux build.

I'm hoping in the future that someone will add support for C++ to one of the concurrent testing tools that C# have, like NCrunch, Giles and ContinuousTests.

Of course, you might find someone writes another adapter for VS2012 that adds QtTest support to Test Adapter in which case this advantage goes away! If anyone is interested in this there's a good blog post Authoring a new Visual studio unit test adapter.

我家小可爱 2024-08-14 06:49:35

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

For Visual Studio test adapter tool support with the QtTest framework use this Visual Studio extension: https://visualstudiogallery.msdn.microsoft.com/cc1fcd27-4e58-4663-951f-fb02d9ff3653

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文