我应该使用什么unit testing框架来用于Qt?

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

我们也需要一个unit testing框架。 直到大约一年前,我们使用内部开发的C ++项目unit testing框架,但是现在我们正在过渡到使用Google Test来进行新项目。

有没有人有任何使用谷歌testingQt应用程序的经验? QtTest / QTestLib是一个更好的select吗?

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

编辑:它看起来像很多倾向于QtTest。 有没有人有任何与持续集成服务器集成的经验? 另外,在我看来,为每个新的testing案例处理一个单独的应用程序会导致很大的摩擦。 有没有什么好方法可以解决这个问题? Qt Creator是否有处理这些testing用例的好方法,或者你需要在每个testing用例中有一个项目吗?

我不知道QTestLib在一般意义上比另一个框架更好。 有一件事很好,那就是testing基于Qt的应用程序的好方法。

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

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

每个testing用例的单个testing应用程序从来没有给我造成过多的摩擦,但这取决于是否有一个构build系统能够完成pipe理testing用例构build和执行的体面工作。

我不知道Qt Creator中的每个testing用例都需要一个独立的项目,但是自从我上一次查看Qt Creator以来它可能已经发生了变化。

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

您不必创build单独的testing应用程序。 只要在独立的main()函数中使用qExec就可以了:

int main(int argc, char *argv[]) { TestClass1 test1; QTest::qExec(&test1, argc, argv); TestClass2 test2; QTest::qExec(&test2, argc, argv); // ... return 0; } 

这将在一个批次中执行每个类中的所有testing方法。

你的testclass .h文件看起来如下所示:

 class TestClass1 : public QObject { Q_OBJECT private slots: void testMethod1(); // ... } 

不幸的是,这个设置在Qt文档中并没有很好的描述,尽pipe它对于很多人来说似乎相当有用。

追加到乔的答案。

这里有一个我使用的小标题(testrunner.h),包含一个产生事件循环的实用类(例如,需要testing排队的信号槽连接和数据库)和“运行”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)我的testing运行速度非常快 – 足够快,加载可执行文件,设置Q(核心)应用程序(如果需要)等等的开销通常会使testing本身的运行时间变得相形见绌! 链接每个可执行文件也占用很多时间。

随着越来越多的课程的增加,开销也不断增加,很快就成了一个问题 – unit testing的目标之一是要有一个安全网,运行速度非常快,根本不是一个负担,这是迅速变得不是这样。 解决方法是将多个testing套件整合到一个可执行文件中,同时(如上所示),这个function大多可用,但不受支持,并具有重要的限制。

2)没有夹具支持 – 对我来说是一个破坏交易的人。

所以过了一段时间,我转向Googletesting – 这是一个function更强大,更复杂的unit testing框架(特别是与Google Mock一起使用)并解决了1)和2),而且,您仍然可以轻松使用方便的QTestLibfunction比如QSignalSpy和GUI事件的模拟等等。切换起来有点痛苦,但是幸运的是这个项目并没有进步太多,许多变化都可以自动化。

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

为什么不使用Qt中包含的unit testing框架呢? 一个例子: QtTestLib教程 。

QtTest主要用于testing需要Qt事件循环/信号调度的零件。 它的devise方式是每个testing用例都需要一个单独的可执行文件,所以它不应该与用于其他应用程序的现有testing框架相冲突。

(顺便说一下,我强烈build议使用QtCore,甚至在应用程序的非GUI部分也是如此,使用起来更好。)

为了扩展mlvljr和Joe的解决scheme,我们甚至可以支持每个testing类的完整的QtTest选项,并且仍然可以在批处理和日志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]]... 

 #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; } 

如果您使用的是Qt,我会推荐使用QtTest,因为它具有testing用户界面的function,使用简单。

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

我使用gtest和QSignalSpytesting了我们的库。 使用QSignalSpy来捕捉信号。 您可以直接调用插槽(如常规方法)来testing它们。

我刚刚玩过这个。 使用Google Test for QtTest的主要优势在于我们在Visual Studio中完成了所有UI开发。 如果您使用Visual Studio 2012并安装Google Test Adapter ,则可以让VS识别testing并将其包含在其testing资源pipe理器中。 这对于开发人员在编写代码时能够使用而言非常有用,而且由于Google Test是可移植的,所以我们也可以将testing添加到我们的Linux版本的最后。

我希望将来有人会将C ++支持添加到C#所具有的并发testing工具之一,如NCrunch , Giles和ContinuousTests 。

当然,你可能会发现有人为VS2012写了另外一个适配器,为testing适配器增加了QtTest支持,在这种情况下,这个优势就消失了! 如果有人对此感兴趣,那么有一篇很好的博客文章正在编写一个新的Visual Studiounit testing适配器 。

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