Я исследовал топи c программирования asyn c с использованием Qt и пришел к выводу, что безопасно излучать сигналы из потоков любого типа (хотя в документах QT упоминается только QThread
), так как больше или менее описанный здесь . Теперь я столкнулся с проблемой тестирования моего приложения. Чтобы максимально упростить: у меня есть операция asyn c, которая может уведомить MainWindow
, испуская СИГНАЛ. Он отлично работает на производстве, но не работает в среде модульного тестирования с QTest
. Полный пример (структура проекта плоская, без подкаталогов):
CMakeLists.txt
cmake_minimum_required(VERSION 3.0.0)
project(QtFailure)
enable_testing()
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
if(CMAKE_VERSION VERSION_LESS "3.7.0")
set(CMAKE_INCLUDE_CURRENT_DIR ON)
endif()
find_package(Qt5 COMPONENTS Core Widgets Test REQUIRED)
add_library(QtFailure source.cpp header.hpp)
target_include_directories(QtFailure PUBLIC .)
target_link_libraries(QtFailure
pthread
Qt5::Core
Qt5::Widgets
)
add_executable(main main.cpp)
target_link_libraries(main QtFailure)
add_executable(QtFailureTest test.cpp)
target_link_libraries(QtFailureTest
QtFailure
Qt5::Test
)
header.hpp
#pragma once
#include <QMainWindow>
#include <QWidget>
#include <future>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget* parent = nullptr);
~MainWindow();
void start();
int counter_value();
signals:
void sendSignal();
private slots:
bool triggerSlot();
private:
bool stop_;
std::future<void> async_oper_;
};
source.cpp
#include "header.hpp"
#include <QMainWindow>
#include <QWidget>
#include <QObject>
#include <QDebug>
#include <future>
#include <chrono>
#include <thread>
static int counter = 0;
MainWindow::MainWindow(QWidget* parent):
QMainWindow(parent),
stop_(false),
async_oper_()
{
QObject::connect(this, SIGNAL(sendSignal()), this, SLOT(triggerSlot()));
}
MainWindow::~MainWindow()
{
stop_ = true;
}
int MainWindow::counter_value()
{
return counter;
}
void MainWindow::start()
{
if (async_oper_.valid()) return;
emit sendSignal(); // this one works
async_oper_ = std::async(std::launch::async, [this]()
{
while (!stop_)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
emit sendSignal(); // this one doesn't work in tests
}
});
}
bool MainWindow::triggerSlot()
{
qDebug() << "triggerSlot: " << counter;
counter++;
}
test.cpp
#include "header.hpp"
#include <QSignalSpy>
#include <QDebug>
#include <QtTest/QtTest>
#include <memory>
#include <chrono>
#include <thread>
class MyFixture: public QObject
{
Q_OBJECT
private:
std::unique_ptr<MainWindow> sut_;
private slots:
void init()
{
qDebug("MyFixture init");
sut_.reset(new MainWindow);
}
void cleanup()
{
qDebug("MyFixture cleanup");
sut_.reset();
}
void example_test()
{
QSignalSpy spy(sut_.get(), SIGNAL(sendSignal()));
sut_->start();
std::this_thread::sleep_for(std::chrono::seconds(1));
qDebug() << "num signals: " << spy.count();
qDebug() << "counter value: " << sut_->counter_value();
}
};
QTEST_MAIN(MyFixture)
#include "test.moc"
main.cpp
#include <QApplication>
#include "header.hpp"
int main(int argc, char** argv)
{
QApplication a(argc, argv);
MainWindow w;
w.start();
w.show();
return a.exec();
}
Результат моего теста
PASS : MyFixture::initTestCase()
QDEBUG : MyFixture::example_test() MyFixture init
QDEBUG : MyFixture::example_test() triggerSlot: 0
QDEBUG : MyFixture::example_test() num signals: 10
QDEBUG : MyFixture::example_test() counter value: 1
QDEBUG : MyFixture::example_test() MyFixture cleanup
PASS : MyFixture::example_test()
PASS : MyFixture::cleanupTestCase()
Totals: 3 passed, 0 failed, 0 skipped, 0 blacklisted, 1003ms
, что означает, что слот был активирован только один раз, по сигналу, исходящему из основного потока.
Выход из основного потока:
...
triggerSlot: 25
triggerSlot: 26
triggerSlot: 27
etc ...
, что является ожидаемым поведением. Почему существует разница между средой QTest и обычным QApplication в main? Что я должен сделать, чтобы исправить поведение тестов?
Я должен использовать стандартные потоки C ++, потому что мой GUI - это просто фасад реальной системы, не связанной с Qt, которая имеет различные типы асин c оперов, callbacks et c.
Извините за количество кода, но с QT я не могу сжать его в несколько строк.
=== РЕДАКТИРОВАТЬ ===
Указание Qt::DirectConnection
атрибута соединения «исправит» проблему в среде QTest. Но это то, что я не могу сделать в большинстве случаев, потому что GUI действия должны выполняться в главном потоке (например, scyn c oper посылает сигнал для запуска QPixmap refre sh);