Цикл событий Qt и модульное тестирование?
Я начал экспериментировать с модульным тестированием в Qt и хотел бы услышать комментарии к сценарию, который включает сигналы модульного тестирования и слоты.
Вот пример:
Код, который я хотел бы проверить (m_socket является указателем на QTcpSocket
):
void CommunicationProtocol::connectToCamera()
{
m_socket->connectToHost(m_cameraIp,m_port);
}
Поскольку это асинхронный вызов, я не могу проверить возвращаемое значение. Однако я хотел бы проверить, если ответный сигнал, который сокет испускает при успешном соединении (void connected ()
) на самом деле испускается.
Я написал тест ниже:
void CommunicationProtocolTest::testConnectToCammera()
{
QSignalSpy spy(communicationProtocol->m_socket, SIGNAL(connected()));
communicationProtocol->connectToCamera();
QTest::qWait(250);
QCOMPARE(spy.count(), 1);
}
Моя мотивация состояла в том, что если ответ не произойдет через 250 мс, что-то не так.
Однако сигнал никогда не улавливается, и я не могу точно сказать, был ли он излучен. Но я заметил, что нигде в тестовом проекте я не запускаю цикл обработки событий. В проекте разработки цикл событий запускается в основном с QCoreApplication::exec()
,
Подводя итог, при модульном тестировании класса, который зависит от сигналов и слотов, где следует
QCoreApplication a(argc, argv);
return a.exec();
запускаться в тестовой среде?
3 ответа
Я понимаю, что это старая ветка, но, как только я ее нажму, и, как и другие, ответа нет, а ответ от Питера и других комментариев все еще не соответствует смыслу использования QSignalSpy.
Чтобы ответить на ваш оригинальный вопрос о том, "где требуется exec-функция QCoreApplication", в основном ответ таков, что это не так. QTest и QSignalSpy уже имеют встроенный.
Что вам действительно нужно сделать в вашем тестовом примере, так это "запустить" существующий цикл обработки событий.
Предполагая, что вы используете Qt 5: http://doc.qt.io/qt-5/qsignalspy.html
Итак, чтобы изменить ваш пример, чтобы использовать функцию ожидания:
void CommunicationProtocolTest::testConnectToCammera()
{
QSignalSpy spy(communicationProtocol->m_socket, SIGNAL(connected()));
communicationProtocol->connectToCamera();
// wait returns true if 1 or more signals was emitted
QCOMPARE(spy.wait(250), true);
// You can be pedantic here and double check if you want
QCOMPARE(spy.count(), 1);
}
Это должно дать вам желаемое поведение без необходимости создания другого цикла событий.
Хороший вопрос. Основные проблемы, с которыми я столкнулся, - это (1) необходимость позволить app делать app.exec(), но все еще близко к концу, чтобы не блокировать автоматические сборки, и (2) необходимость обеспечения обработки ожидающих событий перед использованием результата сигнала / слот звонки.
Для (1) вы можете попробовать закомментировать app.exec() в main(). НО тогда, если у кого-то есть FooWidget.exec() в своем классе, который вы тестируете, он будет блокировать / зависать. Примерно так удобно заставить qApp выйти:
int main(int argc, char *argv[]) {
QApplication a( argc, argv );
//prevent hanging if QMenu.exec() got called
smersh().KillAppAfterTimeout(300);
::testing::InitGoogleTest(&argc, argv);
int iReturn = RUN_ALL_TESTS();
qDebug()<<"rcode:"<<iReturn;
smersh().KillAppAfterTimeout(1);
return a.exec();
}
struct smersh {
bool KillAppAfterTimeout(int secs=10) const;
};
bool smersh::KillAppAfterTimeout(int secs) const {
QScopedPointer<QTimer> timer(new QTimer);
timer->setSingleShot(true);
bool ok = timer->connect(timer.data(),SIGNAL(timeout()),qApp,SLOT(quit()),Qt::QueuedConnection);
timer->start(secs * 1000); // N seconds timeout
timer.take()->setParent(qApp);
return ok;
}
Для (2), в основном, вы должны заставить QApplication завершать события в очереди, если вы пытаетесь проверить такие вещи, как QEvent
S от мыши + клавиатура ожидаемого результата. это FlushEvents<>()
Метод полезен:
template <class T=void> struct FlushEvents {
FlushEvents() {
int n = 0;
while(++n<20 && qApp->hasPendingEvents() ) {
QApplication::sendPostedEvents();
QApplication::processEvents(QEventLoop::AllEvents);
YourThread::microsec_wait(100);
}
YourThread::microsec_wait(1*1000);
} };
Пример использования ниже. "Диалог" является экземпляром MyDialog. "Баз" является экземпляром Баз. "Диалог" имеет член типа Bar. Когда Бар выбирает Баз, он издает сигнал; "Диалог" подключен к сигналу, и мы должны убедиться, что соответствующий слот получил сообщение.
void Bar::select(Baz* baz) {
if( baz->isValid() ) {
m_selected << baz;
emit SelectedBaz();//<- dialog has slot for this
} }
TEST(Dialog,BarBaz) { /*<code>*/
dialog->setGeometry(1,320,400,300);
dialog->repaint();
FlushEvents<>(); // see it on screen (for debugging)
//set state of dialog that has a stacked widget
dialog->setCurrentPage(i);
qDebug()<<"on page: "
<<i; // (we don't see it yet)
FlushEvents<>(); // Now dialog is drawn on page i
dialog->GetBar()->select(baz);
FlushEvents<>(); // *** without this, the next test
// can fail sporadically.
EXPECT_TRUE( dialog->getSelected_Baz_instances()
.contains(baz) );
/*<code>*/
}
У меня была похожая проблема с Qt::QueuedConnection
(событие ставится в очередь автоматически, если отправитель и получатель принадлежат разным потокам). Без надлежащего цикла событий в этой ситуации внутреннее состояние объектов в зависимости от обработки событий не будет обновлено. Чтобы запустить цикл обработки событий при запуске QTest, измените макрос QTEST_APPLESS_MAIN
внизу файла QTEST_MAIN
, Затем, позвонив qApp->processEvents()
будет фактически обрабатывать события, или вы можете начать другой цикл событий с QEventLoop
,
QSignalSpy spy(&foo, SIGNAL(ready()));
connect(&foo, SIGNAL(ready()), &bar, SLOT(work()), Qt::QueuedConnection);
foo.emitReady();
QCOMPARE(spy.count(), 1); // QSignalSpy uses Qt::DirectConnection
QCOMPARE(bar.received, false); // bar did not receive the signal, but that is normal: there is no active event loop
qApp->processEvents(); // Manually trigger event processing ...
QCOMPARE(bar.received, true); // bar receives the signal only if QTEST_MAIN() is used