我有一个子项目,我在其中放置了所有
QTest
单元测试,并构建了一个运行测试的独立测试应用程序(即我从 Qt Creator 中运行它)。我有多个测试类,可以使用 qExec()
执行。但是我不知道执行多个测试类的正确方法是什么。
目前我是这样做的(MVCE):
QT -= gui
QT += core \
testlib
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app
TARGET = testrunner
HEADERS += test_foo.h
SOURCES += main.cpp
#include <QtTest>
#include <QCoreApplication>
#include "test_foo.h"
int main(int argc, char** argv) {
QCoreApplication app(argc, argv);
TestFooClass testFoo;
TestBarClass testBar;
// NOTE THIS LINE IN PARTICULAR.
return QTest::qExec(&testFoo, argc, argv) || QTest::qExec(&testBar, argc, argv);
}
#include <QtTest>
class TestFooClass: public QObject
{
Q_OBJECT
private slots:
void test_func_foo() {};
};
class TestBarClass: public QObject
{
Q_OBJECT
private slots:
void test_func_bar() {};
};
但是qExec()
对于独立测试应用程序,不应多次调用此函数,因为用于将测试输出记录到文件和执行单个测试函数的命令行选项将无法正确运行。
另一个主要缺点是没有针对所有测试类的单一摘要,仅针对个别类。当我有几十个类,每个类都有几十个测试时,这是一个问题。要检查所有测试是否通过,我必须向上滚动以查看每个类通过/失败的所有“总计”,例如:
********* Start testing of TestFooClass *********
PASS : TestFooClass::initTestCase()
PASS : TestFooClass::test_func_foo()
PASS : TestFooClass::cleanupTestCase()
Totals: 3 passed, 0 failed, 0 skipped, 0 blacklisted
********* Finished testing of TestFooClass *********
********* Start testing of TestBarClass *********
PASS : TestBarClass::initTestCase()
PASS : TestBarClass::test_func_bar()
PASS : TestBarClass::cleanupTestCase()
Totals: 3 passed, 0 failed, 0 skipped, 0 blacklisted
********* Finished testing of TestBarClass *********
我也很惊讶我的
qExec() || qExec()
的工作原理,因为 documentation 说如果测试失败 qExec()
返回一个非零值,这应该意味着所有以下 qExec()
调用都不会发生,但这似乎事实并非如此。
运行多个测试类的正确方法是什么?这样我就可以一目了然地看到我所进行的数百个单元测试中是否有任何一个失败了。
我曾经找到一个很好的解决方案,使用一个普通的Qt项目(no
TEMPLATE = subdirs
),它使用宏方法来创建主函数并自动注册所有测试类(宏,也是)一个简单的辅助头文件。
这里是一个示例测试类(只有相关的头文件):
#ifndef FOOTESTS_H
#define FOOTESTS_H
#include "AutoTest.h"
class FooTests : public QObject
{
Q_OBJECT
private slots:
void initTestCase();
void test1();
void test2();
void cleanupTestCase();
};
DECLARE_TEST(FooTests)
#endif // FOOTESTS_H
以及主函数,它消耗以这种方式创建的每个测试类:
#include "AutoTest.h"
TEST_MAIN
AutoTest.h
的代码:
#ifndef AUTOTEST_H
#define AUTOTEST_H
#include <QTest>
#include <QList>
#include <QString>
#include <QSharedPointer>
namespace AutoTest
{
typedef QList<QObject*> TestList;
inline TestList& testList()
{
static TestList list;
return list;
}
inline bool findObject(QObject* object)
{
TestList& list = testList();
if (list.contains(object))
{
return true;
}
foreach (QObject* test, list)
{
if (test->objectName() == object->objectName())
{
return true;
}
}
return false;
}
inline void addTest(QObject* object)
{
TestList& list = testList();
if (!findObject(object))
{
list.append(object);
}
}
inline int run(int argc, char *argv[])
{
int ret = 0;
foreach (QObject* test, testList())
{
ret += QTest::qExec(test, argc, argv);
}
return ret;
}
}
template <class T>
class Test
{
public:
QSharedPointer<T> child;
Test(const QString& name) : child(new T)
{
child->setObjectName(name);
AutoTest::addTest(child.data());
}
};
#define DECLARE_TEST(className) static Test<className> t(#className);
#define TEST_MAIN \
int main(int argc, char *argv[]) \
{ \
return AutoTest::run(argc, argv); \
}
#endif // AUTOTEST_H
所有学分均归Rob Caldecott所有。
要执行单个测试项目中包含的多个测试类,包括测试类的自动检测(只需从
TestClass
派生)并在末尾打印一个漂亮的总结,请使用以下代码:
#include "TestClass.h"
class FooTestClass : public TestClass {
Q_OBJECT
private:
Q_SLOT void fooTest1() { }
Q_SLOT void fooTest2() { }
};
inline FooTestClass fooTests; // Note: C++17 style inline variable
#include "TestClass.h"
class BarTestClass : public TestClass {
Q_OBJECT
private:
Q_SLOT void barTest1() { }
Q_SLOT void barTest2() { }
};
inline BarTestClass barTests; // Note: C++17 style inline variable
#include "TestClass.h"
int main(int argc, char **argv) {
return TestClass::runAllTests(argc, argv);
}
#include <QDebug>
#include <QObject>
#include <QString>
#include <QTest>
class TestClass : public QObject {
Q_OBJECT
private:
static QObjectList &testObjects() { static QObjectList testObjects; return testObjects; }
public:
TestClass() { testObjects().append(this); }
static int runAllTests(int argc, char **argv) {
// Sort test objects by class name.
std::sort(testObjects().begin(), testObjects().end(), [] (const QObject *a, const QObject *b) {
return strcmp(a->metaObject()->className(), b->metaObject()->className()) < 0;
});
// Run all tests.
QStringList results;
int passed = 0;
for (QObject *testObject : testObjects()) {
bool success = false;
try {
success = QTest::qExec(testObject, argc, argv) == EXIT_SUCCESS;
} catch (...) { }
qDebug() << "";
results << QString("%1 : %2").arg(success ? "PASS " : "FAIL!").arg(testObject->metaObject()->className());
passed += success ? 1 : 0;
}
// Print summary.
int tested = testObjects().size();
results << QString("Totals: %1 tested, %2 passed, %3 failed").arg(tested).arg(passed).arg(tested - passed);
results << QString("Result: %1").arg(tested == passed ? "All tests PASSED." : "Some tests FAILED!");
qDebug() << "********* Start of summary *********";
for (const QString &line : results) {
qDebug() << line.toUtf8().data();
}
qDebug() << "********* End of summary *********";
qDebug() << "";
return tested == passed ? EXIT_SUCCESS : EXIT_FAILURE;
}
};
********* Start testing of BarTestClass *********
Config: Using QtTest library 5.15.12, Qt 5.15.12 (x86_64-little_endian-llp64 shared (dynamic) release build; by GCC 13.2.0), windows 10
PASS : BarTestClass::initTestCase()
PASS : BarTestClass::barTest1()
PASS : BarTestClass::barTest2()
PASS : BarTestClass::cleanupTestCase()
Totals: 4 passed, 0 failed, 0 skipped, 0 blacklisted, 2ms
********* Finished testing of BarTestClass *********
********* Start testing of FooTestClass *********
Config: Using QtTest library 5.15.12, Qt 5.15.12 (x86_64-little_endian-llp64 shared (dynamic) release build; by GCC 13.2.0), windows 10
PASS : FooTestClass::initTestCase()
PASS : FooTestClass::fooTest1()
PASS : FooTestClass::fooTest2()
PASS : FooTestClass::cleanupTestCase()
Totals: 4 passed, 0 failed, 0 skipped, 0 blacklisted, 1ms
********* Finished testing of FooTestClass *********
********* Start of summary *********
PASS : BarTestClass
PASS : FooTestClass
Totals: 2 tested, 2 passed, 0 failed
Result: All tests PASSED.
********* End of summary *********
QTest::qExec的文档说这个函数不应该被多次调用,因为“用于将测试输出记录到文件和执行单个测试函数的命令行选项将无法正确运行”。
确实如此,但这只是一个小问题。请记住,QtTest 命令行选项中的一些将无法按预期工作。如果您根本不传递任何命令行选项,那么无论如何都没有问题。