hacer - ¿Prueba de bucle y unidad de evento Qt?
pruebas de caja blanca (3)
Buena pregunta. Los principales problemas que he detectado son (1) que deben dejar que la aplicación haga app.exec () pero que aún no se hayan bloqueado las construcciones automáticas y (2) que se haya asegurado de que los eventos pendientes se procesen antes de confiar en el resultado de la señal / llamadas de ranura.
Para (1), puedes intentar comentar el app.exec () en main (). PERO entonces, si alguien tiene FooWidget.exec () en su clase que está probando, se bloqueará / bloqueará. Algo como esto es útil para forzar qApp para salir:
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;
}
Para (2), básicamente tienes que obligar a QApplication a terminar los eventos en cola si estás tratando de verificar que cosas como QEvent
s de Mouse + Keyboard tienen un resultado esperado. Este FlushEvents<>()
es útil:
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);
} };
Ejemplo de uso a continuación. "dialog" es una instancia de MyDialog. "baz" es instancia de baz. "diálogo" tiene un miembro de tipo barra. Cuando una Barra selecciona un Baz, emite una señal; El "diálogo" está conectado a la señal y debemos asegurarnos de que la ranura asociada haya recibido el mensaje.
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>*/
}
Empecé a experimentar con pruebas de unidad en Qt y me gustaría escuchar comentarios sobre un escenario que involucra señales y ranuras de prueba de unidad.
Aquí hay un ejemplo:
El código que me gustaría probar es (m_socket es un puntero a QTcpSocket
):
void CommunicationProtocol::connectToCamera()
{
m_socket->connectToHost(m_cameraIp,m_port);
}
Como esa es una llamada asíncrona, no puedo probar un valor devuelto. Sin embargo, me gustaría probar si la señal de respuesta que emite el socket en una conexión exitosa ( void connected ()
) es de hecho emitida.
He escrito la prueba a continuación:
void CommunicationProtocolTest::testConnectToCammera()
{
QSignalSpy spy(communicationProtocol->m_socket, SIGNAL(connected()));
communicationProtocol->connectToCamera();
QTest::qWait(250);
QCOMPARE(spy.count(), 1);
}
Mi motivación fue que si la respuesta no ocurre en 250 ms, algo está mal.
Sin embargo, la señal nunca se captura, y no puedo decir con seguridad si es emitida. Pero me he dado cuenta de que no estoy iniciando el bucle de eventos en ningún lugar del proyecto de prueba. En el proyecto de desarrollo, el bucle de eventos se inicia principalmente con QCoreApplication::exec()
.
Para resumir, cuando la unidad prueba una clase que depende de señales y ranuras, ¿dónde debería el
QCoreApplication a(argc, argv);
return a.exec();
ser ejecutado en el entorno de prueba?
Me doy cuenta de que este es un tema antiguo, pero cuando lo golpeo y como otros lo harán, no hay respuesta y la respuesta de Peter y otros comentarios aún no llegan al punto de usar QSignalSpy.
Para responder a su pregunta original acerca de "donde se necesita la función exec QCoreApplication", básicamente la respuesta es que no lo es. QTest y QSignalSpy ya tienen eso incorporado.
Lo que realmente necesita hacer en su caso de prueba es "ejecutar" el bucle de eventos existente.
Suponiendo que está utilizando Qt 5: http://doc.qt.io/qt-5/qsignalspy.html#wait
Así que para modificar su ejemplo para usar la función de espera:
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);
}
Eso debería darle el comportamiento deseado sin tener que crear otro bucle de eventos.
Tuve un problema similar con Qt::QueuedConnection
(el evento se pone en cola automáticamente si el remitente y el receptor pertenecen a diferentes subprocesos). Sin un bucle de eventos adecuado en esa situación, el estado interno de los objetos que dependen del procesamiento de eventos no se actualizará. Para iniciar un bucle de eventos al ejecutar QTest, cambie la macro QTEST_APPLESS_MAIN
en la parte inferior del archivo a QTEST_MAIN
. Luego, al llamar a qApp->processEvents()
realidad se procesarán eventos, o puede iniciar otro ciclo de eventos con 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