33

I have a Qt Unit test (sub)project, which generates me one class (with the main generated by QTEST_APPLESS_MAIN).I can start this from within Qt Creator as console app.

Q: How would I add additional classes as test cases to this particular project.

  1. If these classes only have "test" slots (private Q_SLOTS), the methods are not called, but just the ones of the class with QTEST_APPLESS_MAIN
  2. Since there can be only one main(..), I cannot use QTEST_APPLESS_MAIN with more than one class in the project (is that correct?)
  3. Of course, I can manually "wire" the slots in the (additional) classes with the one class containing the main, but this is very tedious.

So what is the best way to run unit test over several classes in a unit test project?

PS: In " Using QT Unit Tests in a project - conflicting main(...) functions " a Blog is mentioned, however, I cannot download the zip describing the solution.

Qt Unit Test subproject

Community
  • 1
  • 1
Horst Walter
  • 13,663
  • 32
  • 126
  • 228

6 Answers6

25

As per the solution you linked to, the way to accomplish testing two (or more) classes within a single Qt unit test project is to ensure that each class to be tested has a corresponding test class, and that you've created a custom int main that executes each test class.

For example:

class TestClassA : public QObject
{
   Q_OBJECT
public:
   TestClassA();

   ...

private Q_SLOTS:
   void testCase1();
   ...
};

class TestClassB : public QObject
{
   Q_OBJECT
public:
   TestClassB();

   ...

private Q_SLOTS:
   void testCase2();
   ...
};

void TestClassA::testCase1()
{
   // Define test here.
}

void TestClassB::testCase2()
{
   // Define test here.
}

// Additional tests defined here.

// Note: This is equivalent to QTEST_APPLESS_MAIN for multiple test classes.
int main(int argc, char** argv)
{
   int status = 0;
   {
      TestClassA tc;
      status |= QTest::qExec(&tc, argc, argv);
   }
   {
      TestClassB tc;
      status |= QTest::qExec(&tc, argc, argv);
   }
   return status;
}

Obviously, the different test classes can be spread out over multiple translation units, then simply included in the translation unit with your int main. Don't forget to include the appropriate .moc files.

RA.
  • 7,542
  • 1
  • 34
  • 35
  • Straight forward, should have looked at the QTEST_APPLESS_MAIN DEFINE sooner. – Horst Walter Aug 31 '12 at 10:35
  • I just noticed you are the one who answered this question, who also commented on my other question earlier, so i suspect you are in a good position to explain the difference. – johnbakers Oct 08 '13 at 03:45
  • You don't need to include a .moc if you have 2 separate files for your test (.cpp and .h). – Patapoom May 02 '19 at 12:16
  • I wouldn't like working in a project set up like that. It breaks passing a method name on the command line, to debug a failing test without first going through all other test methods. – David Faure Dec 06 '21 at 14:21
21

Based in the accepted answer and if you are using C++11 you could be interested in a solution using lambdas. It avoids you write the same code everytime. Although you can replace the lambda with a function, I think a lambda is cleaner.

#include <QtTest>

#include "test1.h"
#include "test2.h"


int main(int argc, char** argv)
{
   int status = 0;
   auto ASSERT_TEST = [&status, argc, argv](QObject* obj) {
     status |= QTest::qExec(obj, argc, argv);
     delete obj;
   };

   ASSERT_TEST(new Test1());
   ASSERT_TEST(new Test2());

   return status;
}

#ifndef TEST1_H
#define TEST1_H

Sample test

#include <QtTest>

class Test1 : public QObject
{
    Q_OBJECT

  private Q_SLOTS:
    void testCase1();
};
Phidelux
  • 2,043
  • 1
  • 32
  • 50
Edwin Rodríguez
  • 1,229
  • 10
  • 19
  • 1
    What is the reason for creating the objects dynamically? You could also pass a reference, then you don't have to handle object destruction. `auto ASSERT_TEST = [&status, argc, argv](QObject &obj) { status |= QTest::qExec(&obj, argc, argv); }; ASSERT_TEST(Test1());` – Christophe Weis May 26 '16 at 06:32
  • @ChristopheWeis I just prefer to use pointer when using `QObjects` to be sure to not mess with their lifetime in event loops, in that case a `obj->deleteLater()` would be better than a `delete obj`. But I think there is nothing wrong with your suggestion, it should work too – Edwin Rodríguez May 27 '16 at 14:11
  • With the methods suggested, the QtCreator GUI is not able to integrate with the tests. Apparently QtCreator needs `QTEST_MAIN` to do so. – Megidd Aug 08 '19 at 13:10
9

Searching for this same answer, I found a very good solution from http://qtcreator.blogspot.de/2009/10/running-multiple-unit-tests.html. He creates a namespace with a container that registers all the tests created (via the DECLARE_TEST macro), and then uses it to run all the tests on the list. I rewrote it to fit my code and I post my version here (My Qt Creator version: 4.1.0):

/* BASED ON
 * http://qtcreator.blogspot.de/2009/10/running-multiple-unit-tests.html
 */    
#ifndef TESTCOLLECTOR_H
#define TESTCOLLECTOR_H

#include <QtTest>
#include <memory>
#include <map>
#include <string>

namespace TestCollector{
typedef std::map<std::string, std::shared_ptr<QObject> > TestList;
inline TestList& GetTestList()
{
   static TestList list;
   return list;
}

inline int RunAllTests(int argc, char **argv) {
    int result = 0;
    for (const auto&i:GetTestList()) {
        result += QTest::qExec(i.second.get(), argc, argv);
    }
    return result;
}

template <class T>
class UnitTestClass {
public:
    UnitTestClass(const std::string& pTestName) {
        auto& testList = TestCollector::GetTestList();
        if (0==testList.count(pTestName)) {
            testList.insert(std::make_pair(pTestName, std::make_shared<T>()));
        }
    }
};
}

#define ADD_TEST(className) static TestCollector::UnitTestClass<className> \
    test(#className);

#endif // TESTCOLLECTOR_H

Then, just add the ADD_TEST(class) line in your test header like this:

#ifndef TESTRANDOMENGINES_H
#define TESTRANDOMENGINES_H

#include <QtTest>
#include "TestCollector.h"

class TestRandomEngines : public QObject
{
    Q_OBJECT

private Q_SLOTS:
    void test1();
};

ADD_TEST(TestRandomEngines)

#endif // TESTRANDOMENGINES_H

And and to run all the tests, just do:

#include "TestCollector.h"
#include <iostream>

int main(int argc, char *argv[]) {
    auto nFailedTests = TestCollector::RunAllTests(argc, argv);
    std::cout << "Total number of failed tests: "
              << nFailedTests << std::endl;
    return nFailedTests;
}
Aba
  • 157
  • 2
  • 11
3

I'm using the following code to collect all test results:

#include "testclassa.h"
#include "testclassb.h"
#include <QtTest>
#include <QDebug>

int main(int argc, char** argv){

    int failedTests = 0;
    TestClassA testClassA
    TestClassB testClassB
    failedTests += QTest::qExec(&testClassA, argc, argv);
    failedTests += QTest::qExec(&testClassB, argc, argv);

    if(failedTests > 0){
        qDebug() << "total number of failed tests: " << failedTests;
    }else{
        qDebug() << "all tests passed :)";
    }
    return failedTests;
}
2

Build with CMake and not QMake, and add two test targets.

add_executable(firstTest tst_testfirst.cpp)
add_test(NAME firstTest COMMAND firstTest)

add_executable(secondTest tst_testsecond.cpp)
add_test(NAME secondTest COMMAND secondTest)

Both tst_testfirst.cpp and tst_testsecond.cpp have their own QTEST_MAIN lines.

Qt Creator will run both test classes. If you're running them from the command line, you run the tests with "ctest".

user240515
  • 3,056
  • 1
  • 27
  • 34
1

The way I do it:

  • Create a general "subdirs" project.
  • Put the code under test in a C++ library subproject.
  • Instead of using a unit test project, I use a console application subproject.
  • Link the library to this console application, don't forget to handle the dependencies in the .pro file at the top of the hierarchy.
  • In this console subproject, define as many test classes as you wish, and launch them in the main of this same project.

I basically made a slight variation of this post.

Doplusplus
  • 21
  • 4