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
相关产品推荐
相关产品推荐

