Unit testing 我应该为Qt使用什么单元测试框架?
我刚刚开始一个新项目,需要一些跨平台的GUI,我们选择了Qt作为GUI框架 我们也需要一个单元测试框架。直到大约一年前,我们还在C++项目中使用内部开发的单元测试框架,但现在我们正在过渡到在新项目中使用GoogleTestUnit testing 我应该为Qt使用什么单元测试框架?,unit-testing,qt,googletest,qtestlib,qttest,Unit Testing,Qt,Googletest,Qtestlib,Qttest,我刚刚开始一个新项目,需要一些跨平台的GUI,我们选择了Qt作为GUI框架 我们也需要一个单元测试框架。直到大约一年前,我们还在C++项目中使用内部开发的单元测试框架,但现在我们正在过渡到在新项目中使用GoogleTest 有没有人有过在Qt应用程序中使用GoogleTest的经验?qtest/QTestLib是更好的选择吗 我仍然不确定我们希望在项目的非GUI部分中使用Qt的程度-我们可能更希望在核心代码中使用STL/Boost,并为基于Qt的GUI提供一个小型接口 编辑:看起来很多人都倾向于
有没有人有过在Qt应用程序中使用GoogleTest的经验?qtest/QTestLib是更好的选择吗 我仍然不确定我们希望在项目的非GUI部分中使用Qt的程度-我们可能更希望在核心代码中使用STL/Boost,并为基于Qt的GUI提供一个小型接口
编辑:看起来很多人都倾向于测试。有没有人有将其与持续集成服务器集成的经验?此外,在我看来,必须为每个新的测试用例处理单独的应用程序会造成很大的摩擦。有什么好办法解决这个问题吗?Qt Creator是否有处理此类测试用例的好方法,或者每个测试用例都需要一个项目?为什么不使用Qt中包含的单元测试框架?
例如:.QtTest主要用于测试需要Qt事件循环/信号调度的部件。它的设计方式是,每个测试用例都需要一个单独的可执行文件,因此它不应该与应用程序其余部分使用的任何现有测试框架冲突
(顺便说一句,我强烈建议即使对于应用程序的非GUI部分也使用QtCore。使用它会更好。)如果您使用的是Qt,我建议使用QtTest,因为它具有测试UI的功能,并且使用简单
如果您使用QtCore,则可能不需要STL。我经常发现Qt类比STL类更容易使用。我不知道QTestLib在一般意义上比一个框架更好。有一件事它做得很好,那就是提供了一种测试基于Qt的应用程序的好方法 您可以将QTest集成到新的基于Google测试的设置中。我还没有尝试过,但基于QTestLib的架构,它似乎不会太复杂 使用纯QTestLib编写的测试有一个-xml选项,您可以使用该选项以及一些XSLT转换来转换为持续集成服务器所需的格式。然而,这在很大程度上取决于您使用的CI服务器。我想这同样适用于GTest 每个测试用例使用一个测试应用程序从来不会给我带来太多的摩擦,但这取决于是否有一个构建系统能够很好地管理测试用例的构建和执行 我不知道QtCreator中有什么东西需要每个测试用例都有一个单独的项目,但是自从我上次查看QtCreator以来,它可能已经改变了
我还建议坚持使用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
\ifndef TESTRUNNER\u H
#定义TESTRUNNER_H
#包括
#包括
#包括
#包括
类TestRunner:公共QObject
{
Q_对象
公众:
TestRunner()
:m_总体结果(0)
{}
无效添加测试(QObject*测试){
测试->设置父项(此项);
m_测试。附加(测试);
}
bool运行测试(){
int argc=0;
char*argv[]={0};
QCore应用程序应用程序(argc、argv);
QTimer::singleShot(0,this,SLOT(run());
app.exec();
返回m_overallResult==0;
}
专用插槽:
无效运行(){
doRunTests();
QCoreApplication::instance()->quit();
}
私人:
void doRunTests(){
foreach(QObject*测试、m_测试){
m|u总体结果|=QTest::qExec(测试);
}
}
QList m_测试;
int m_总体结果;
};
#endif//TESTRUNNER\u 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;
}
#包括“testrunner.h”
#在此处为您的QTest兼容类包含“…”//标题
#包括
int main(){
TestRunner TestRunner;
testRunner.addTest(新…());//此处是您的QTest兼容类
qDebug()我开始在我的应用程序中使用QtTest,但很快就开始受到限制。两个主要问题是:
1) 我的测试运行得非常快-足够快,以至于加载可执行文件、设置Q(核心)应用程序(如果需要)等的开销通常会使测试本身的运行时间相形见绌!链接每个可执行文件也会占用大量时间
随着越来越多的类被添加,开销一直在增加,这很快就成了一个问题——单元测试的目标之一就是要有一个运行速度如此之快的安全网,而这根本不是一个负担,而且情况很快就不是这样了。解决方案是将多个测试套件集成到一个可执行文件中,而同时(如上所示)这基本上是可以做到的,而且有重要的局限性
2) 没有固定设备支持-对我来说是一个交易破坏者
所以过了一段时间,我转向了谷歌测试——它是一个功能更强大、更复杂的单元测试框架(特别是与谷歌模拟一起使用时),解决了1)和2),而且,你
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;
}