c++qtqtest

How does QTEST_MAIN set everything up automatically to run tests?


I'm working on an existing project written in C++, the entry point for the application is:

QTEST_MAIN(className)

From the documentation I've read, this will create a standard C main() function, but it isn't at all clear on how the application tests are called or what the call order is or how its set-up.

Looking at the class in the project I have, there is no class constructor; the class itself is derived from QObject. It has 23 private slots: one of these is called "initTestCase"; the others are various tests all ending in "Test".

The slot "InitTestCase" contains a single call to setup logging filter rules and that is all. When the project is compiled and run, it executes tests, but I cannot see how or where the order comes from.

What is the macro QTEST_MAIN actually doing in my program, how are the slots being set up, and how does it know which tests to execute?


Solution

  • QTEST_MAIN forwards to an QTEST_MAIN_IMPL:

    #define QTEST_MAIN(TestObject) \
    int main(int argc, char *argv[]) \
    { \
        QTEST_MAIN_IMPL(TestObject) \
    }
    

    This QTEST_MAIN_IMPL is different depending on what QApplication you need (Widgets, Gui or Core). For Widgets, it looks like this:

    #define QTEST_MAIN_IMPL(TestObject) \
        TESTLIB_SELFCOVERAGE_START(#TestObject) \
        QT_PREPEND_NAMESPACE(QTest::Internal::callInitMain)<TestObject>(); \
        QApplication app(argc, argv); \
        app.setAttribute(Qt::AA_Use96Dpi, true); \
        QTEST_DISABLE_KEYPAD_NAVIGATION \
        TestObject tc; \
        QTEST_SET_MAIN_SOURCE_PATH \
        return QTest::qExec(&tc, argc, argv);
    

    QTest::qExec is defined qtestcase.cpp:

    int QTest::qExec(QObject *testObject, int argc, char **argv)
    {
        qInit(testObject, argc, argv);
        int ret = qRun();
        qCleanup();
        return ret;
    }
    

    In qInit(), currentTestObject is set.

    In qRun(), a TestMethods instance is created, and it its constructor, we find this loop:

    const QMetaObject *metaObject = o->metaObject();
    const int count = metaObject->methodCount();
    m_methods.reserve(count);
    for (int i = 0; i < count; ++i) {
         const QMetaMethod me = metaObject->method(i);
         if (isValidSlot(me))
             m_methods.push_back(me);
    }
    

    isValidSlot() is implemented thusly:

    static bool isValidSlot(const QMetaMethod &sl)
    {
        if (sl.access() != QMetaMethod::Private || sl.parameterCount() != 0
            || sl.returnType() != QMetaType::Void || sl.methodType() != QMetaMethod::Slot)
            return false;
        const QByteArray name = sl.name();
        return !(name.isEmpty() || name.endsWith("_data")
            || name == "initTestCase" || name == "cleanupTestCase"
            || name == "init" || name == "cleanup");
    }
    

    Finally, TestMethods::invokeMethod() is called which explicitly checks for an initTestCase first and runs it:

    QTestResult::setCurrentTestFunction("initTestCase");
    if (m_initTestCaseDataMethod.isValid())
        m_initTestCaseDataMethod.invoke(testObject, Qt::DirectConnection);
    

    Likewise, it checks for cleanupTestCase at the end.