You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Qt单元测试中线程任务延迟执行及等待方法咨询

Qt单元测试中强制等待所有线程任务完成的方案

我正在开发Qt应用的单元测试,应用采用单个工作线程处理所有数据库调用,所有数据库方法均通过并发调用返回QFuture。服务类获取QFuture后通常会在任务完成时发射信号,但无法保证所有方法都遵循此逻辑。目前发现线程任务仅在所有测试结束后才执行:对于返回QFuture的方法,我已通过waitForFinished解决等待问题,但内部处理QFuture的方法,其线程任务仍延迟到测试结束后执行。想请教:如何强制等待所有线程任务执行完成?已知可使用spy.wait,但不确定所有方法都会发射信号,是否存在其他等待线程的方式?

测试日志

********* Start testing of Test *********
Config: Using QtTest library 6.5.3, Qt 6.5.3
QINFO  : Test::initTestCase() Database Connected!
PASS   : Test::initTestCase()
FAIL!  : Test::isDataInserted() Compared values are not the same
   Actual   (spy.count()): 0
   Expected (1)          : 1
/test/tests.cpp(36) : failure location
PASS   : Test::cleanupTestCase()
QDEBUG : Test::isDataInserted() Running query on  QThreadPoolThread(0x168, name = "Database ThreadPool")
Totals: 2 passed, 1 failed, 0 skipped, 0 blacklisted, 10ms
********* Finished testing of Test *********
Query executed on  QThreadPoolThread(0x16828586790, name = "Database ThreadPool")
Query result:  QVariant(qlonglong, 4)
A crash occurred \tests.exe.
Function time: 133ms Total time: 142ms

Exception address: 0x00007ff8fa1612e7
Exception code   : 0xc0000005

相关代码示例

tests.cpp

#include <QCoreApplication>
#include <QDebug>
#include <QtTest>

#include "exception.h"
#include "service.h"

class Test : public QObject {
  Q_OBJECT

  const QString dbPath = "test_DB.db";

 private slots:
  void initTestCase() {
    DbManager* db = new DbManager();
    QFuture<void> connection = db->connect(dbPath);

    QFuture test = connection.then([]() { qInfo() << "Database Connected!"; })
                       .onFailed([](Exception e) {
                         qDebug() << "Error initializing database" << e.what();
                       });  // None of this ever runs.

    test.waitForFinished();
  }

  // void cleanupTestCase() {
  //   QFile file(dbPath);
  //   file.remove();
  // }

  void isDataInserted() {
    Service service;
    QSignalSpy spy(&service, SIGNAL(dataInserted()));
    service.insertData("test 1");

    QCOMPARE(spy.count(), 1);
  }
};

QTEST_MAIN(Test)

#include "tests.moc"

dbmanager.h

#ifndef DBMANAGER_H
#define DBMANAGER_H

#include <QFuture>
#include <QSqlQuery>
#include <QtConcurrent/QtConcurrent>

#include "exception.h"
#include "thread.h"

class DbManager : public QObject {
  Q_OBJECT
 public:
  QFuture<void> connect(const QString &path) {
    return QtConcurrent::run(DbThread::instance()->pool(), [path]() {
      QSqlDatabase m_db = QSqlDatabase::addDatabase("QSQLITE", "main");
      m_db.setDatabaseName(path);

      if (!m_db.open()) {
        throw Exception("Error connecting to the database");
      }

      QSqlQuery query(m_db);

      query.prepare(
          "CREATE TABLE IF NOT EXISTS TEST "
          "(id INTEGER PRIMARY KEY AUTOINCREMENT, data TEXT)");
      bool success = query.exec();

      if (!success) {
        throw Exception("Error creating structure");
      }
    });
  }

  QFuture<QSqlQuery> executeQuery(QString queryString, QList<QVariant> args) {
    return QtConcurrent::run(
        DbThread::instance()->pool(), [this, queryString, args]() {
          qDebug() << "Running query on " << QThread::currentThread();
          QSqlQuery query(QSqlDatabase::database("main"));
          query.prepare(queryString);

          for (int i = 0; i < args.length(); i++) {
            query.addBindValue(args[i]);
          }

          if (query.exec()) {
            return query;
          } else {
            throw Exception("Error executing query (" + queryString + ")");
          }
        });
  }
};

#endif  // DBMANAGER_H

service.h

#ifndef SERVICE_H
#define SERVICE_H

#include <QObject>

#include "dbmanager.h"

class Service : public QObject {
  Q_OBJECT

 public:
  void insertData(QString data) {
    QString queryString = "INSERT INTO TEST (data) VALUES (?)";
    auto args = QList<QVariant>{data};

    DbManager db;
    QFuture<QSqlQuery> result = db.executeQuery(queryString, args);

    result
        .then([this](QSqlQuery query) {
          qDebug() << "Query executed on " << QThread::currentThread();
          qDebug() << "Query result: " << query.lastInsertId();
          emit dataInserted();
        })
        .onFailed([this](Exception e) {
          qDebug() << "Error executing query: " << e.what();
          emit dataInsertedFailed();
        });
  }

 signals:
  void dataInserted();
  void dataInsertedFailed();
};

#endif  // SERVICE_H

exception.h

#ifndef EXCEPTION_H
#define EXCEPTION_H

#include <QException>

class Exception : public QException {
 public:
  Exception(QString text) { m_text = text.toLocal8Bit(); }

  const char* what() const noexcept {
    return m_text.data();
  }

  void raise() const {
    Exception e = *this;
    throw e;
  }

  Exception* clone() const { return new Exception(*this); }

 private:
  QByteArray m_text;
};

#endif  // EXCEPTION_H

thread.h

#ifndef THREAD_H
#define THREAD_H

#include <QThreadPool>

class DbThread
{
    DbThread() {
        m_pool = new QThreadPool();
        m_pool->setMaxThreadCount(1);
        m_pool->setExpiryTimeout(-1);
        m_pool->setObjectName("Database ThreadPool");
    };

    virtual ~DbThread() {
        m_pool->deleteLater();
    };

public:
    DbThread( const DbThread& ) = delete;
    DbThread& operator=( const DbThread& ) = delete;

    static DbThread* instance() {

        if ( !m_instance )
            m_instance = new DbThread();

        return m_instance;
    }

    QThreadPool* pool() const { return m_pool; };

private:
    inline static DbThread* m_instance = nullptr;
    QThreadPool* m_pool;

};

#endif // THREAD_H

CMakeLists.txt

cmake_minimum_required(VERSION 3.5)

project(tests LANGUAGES CXX)

enable_testing()

find_package(Qt6 6.5 REQUIRED COMPONENTS Sql Gui Quick Test)

set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_library(sources STATIC
    dbmanager.h thread.h exception.h
    service.h
)

add_executable(tests tests.cpp)
add_test(NAME tests COMMAND tests)

target_link_libraries(sources
    Qt6::Sql
)

target_link_libraries(tests
    PRIVATE Qt6::Quick
    PRIVATE Qt6::Gui
    Qt6::Test
    sources
)

解决方案

方法一:直接等待数据库线程池完成所有任务

所有数据库操作都在自定义的DbThread单例线程池中执行,直接调用线程池的waitForDone()方法即可等待所有任务完成,无需依赖信号:

修改isDataInserted测试用例:

void isDataInserted() {
    Service service;
    QSignalSpy spy(&service, SIGNAL(dataInserted()));
    service.insertData("test 1");

    // 等待线程池所有任务执行完毕
    DbThread::instance()->pool()->waitForDone();
    QCOMPARE(spy.count(), 1);
}

方法二:改造Service层返回QFuture

如果不想直接依赖线程池,可以修改Service::insertData方法,让它返回QFuture,测试代码即可通过waitForFinished()等待任务完成:

修改service.h:

QFuture<void> insertData(QString data) {
    QString queryString = "INSERT INTO TEST (data) VALUES (?)";
    auto args = QList<QVariant>{data};

    DbManager db;
    QFuture<QSqlQuery> result = db.executeQuery(queryString, args);

    return result
        .then([this](QSqlQuery query) {
          qDebug() << "Query executed on " << QThread::currentThread();
          qDebug() << "Query result: " << query.lastInsertId();
          emit dataInserted();
        })
        .onFailed([this](Exception e) {
          qDebug() << "Error executing query: " << e.what();
          emit dataInsertedFailed();
        });
}

测试用例中调用:

void isDataInserted() {
    Service service;
    QSignalSpy spy(&service, SIGNAL(dataInserted()));
    auto future = service.insertData("test 1");

    future.waitForFinished();
    QCOMPARE(spy.count(), 1);
}

方法三:使用QSignalSpy的wait方法(针对有信号的场景)

如果确定方法会发射信号,可使用spy.wait()设置超时,比直接线程池等待更灵活:

void isDataInserted() {
    Service service;
    QSignalSpy spy(&service, SIGNAL(dataInserted()));
    service.insertData("test 1");

    // 等待最多5秒,直到信号触发
    if(spy.wait(5000)){
        QCOMPARE(spy.count(), 1);
    } else {
        QFAIL("Timeout waiting for dataInserted signal");
    }
}

额外修复:initTestCase中的问题

initTestCase中DbManager用new创建后未释放会导致内存泄漏,建议改为栈对象;同时确保then链的QFuture正确等待:

void initTestCase() {
    DbManager db;
    QFuture<void> connection = db.connect(dbPath);

    auto test = connection.then([]() { qInfo() << "Database Connected!"; })
                       .onFailed([](Exception e) {
                         qDebug() << "Error initializing database" << e.what();
                       });

    test.waitForFinished();
}

内容的提问来源于stack exchange,提问作者Arthur Caccavo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 21:32:01