Qt:如何组织具有多个类的单元测试?

33

我有一个Qt单元测试项目(子项目),它给我生成了一个类(主要由QTEST_APPLESS_MAIN生成)。我可以在Qt Creator中将其作为控制台应用程序启动。

问题:如何将其他类作为测试用例添加到这个特定项目中?

  1. 如果这些类只有“test”插槽(private Q_SLOTS),则方法不会被调用,而仅调用带有QTEST_APPLESS_MAIN的类的方法。
  2. 由于只能有一个main(..)函数,因此我不能在项目中使用QTEST_APPLESS_MAIN来处理多个类(这样正确吗?)
  3. 当然,我可以手动“连接”(additional)类中的插槽与包含main的类中的插槽,但这很繁琐。

那么,在单元测试项目中运行多个类的单元测试的最佳方法是什么?

PS: 在“Using QT Unit Tests in a project - conflicting main(...) functions”中提到了一篇博客,但我无法下载描述解决方案的zip文件。

Qt单元测试子项目

6个回答

25

根据你提供的解决方案,实现在单个Qt单元测试项目中测试两个或更多类的方法是确保每个要测试的类都有一个相应的测试类,并创建一个自定义的int main函数来执行每个测试类。

例如:

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

显然,不同的测试类可以分布在多个翻译单元中,然后只需包含在包含 int main 的翻译单元中。别忘了包含适当的 .moc 文件。


直截了当地说,应该早点看 QTEST_APPLESS_MAIN 宏定义。 - Horst Walter
我刚刚注意到你是回答了这个问题的人,也在之前评论了我的另一个问题,所以我猜想你有能力解释其中的区别。 - johnbakers
如果您的测试有两个单独的文件(.cpp和.h),则不需要包含.moc文件。 - Patapoom
我不喜欢像那样设置项目。这会破坏通过命令行传递方法名称来调试失败的测试,而无需先经过所有其他测试方法。 - David Faure

21

基于最佳答案,如果您正在使用C++11,您可能对使用lambda表达式的解决方案感兴趣。它可以避免您每次编写相同的代码。虽然您可以将lambda表达式替换为函数,但我认为lambda表达式更加简洁。

#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

示例测试

#include <QtTest>

class Test1 : public QObject
{
    Q_OBJECT

  private Q_SLOTS:
    void testCase1();
};

1
创建动态对象的原因是什么?您也可以传递引用,这样就不必处理对象销毁。auto ASSERT_TEST = [&status, argc, argv](QObject &obj) { status |= QTest::qExec(&obj, argc, argv); }; ASSERT_TEST(Test1()); - Christophe Weis
@ChristopheWeis 我使用指针时更加偏爱 QObjects,以确保不会在事件循环中干扰它们的生命周期,这种情况下 obj->deleteLater()delete obj 更好。但是我认为你的建议也没有问题,应该也能正常工作。 - Edwin Rodríguez
通过建议的方法,Qt Creator GUI 无法与测试集成。显然,Qt Creator 需要 QTEST_MAIN 来实现这一点。 - user3405291

9
在寻找同样答案的过程中,我从http://qtcreator.blogspot.de/2009/10/running-multiple-unit-tests.html找到了一个非常好的解决方案。他创建了一个命名空间,其中包含一个容器,用于注册所有已创建的测试(通过DECLARE_TEST宏),然后使用它来运行列表中的所有测试。我对其进行了重写以适应我的代码,并在此处发布我的版本(我的Qt Creator版本: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

然后,在您的测试头文件中添加ADD_TEST(class)行,例如:
#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

要运行所有测试,只需执行以下操作:

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

3
我使用以下代码收集所有测试结果:
#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
使用CMake进行构建,而不是QMake,并添加两个测试目标。
add_executable(firstTest tst_testfirst.cpp)
add_test(NAME firstTest COMMAND firstTest)

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

tst_testfirst.cpp和tst_testsecond.cpp都有自己的QTEST_MAIN行。

Qt Creator将运行两个测试类。如果您从命令行运行它们,可以使用"ctest"运行测试。


1
我是这样做的:
  • 创建一个通用的“subdirs”项目。
  • 将要测试的代码放在一个C++库子项目中。
  • 不使用单元测试项目,而是使用控制台应用程序子项目。
  • 将库链接到此控制台应用程序中,在层次结构顶部的.pro文件中处理依赖项时不要忘记。
  • 在此控制台子项目中,定义尽可能多的测试类,并在同一项目的主函数中启动它们。

我基本上对this post进行了轻微的变化。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接