2014-02-06 13 views
12

Ich begann mit Unit Tests in Qt zu experimentieren und würde gerne Kommentare zu einem Szenario hören, bei dem Unit Testsignale und Slots getestet wurden.Qt Ereignisschleife und Komponententest?

Hier ein Beispiel:

Der Code i ist testen möchte (m_socket ist ein Zeiger auf QTcpSocket): Ich kann ein zurückgekehrter testen

void CommunicationProtocol::connectToCamera() 
{ 
    m_socket->connectToHost(m_cameraIp,m_port); 
} 

Da dies ein asynchroner Aufruf Wert. Ich würde jedoch gerne testen, ob tatsächlich das Antwortsignal ausgegeben wird, das die Steckdose bei einer erfolgreichen Verbindung (void connected()) aussendet.

Ich habe den Test unten geschrieben:

void CommunicationProtocolTest::testConnectToCammera() 
{ 
    QSignalSpy spy(communicationProtocol->m_socket, SIGNAL(connected())); 
    communicationProtocol->connectToCamera(); 
    QTest::qWait(250); 
    QCOMPARE(spy.count(), 1); 
} 

Meine Motivation war, wenn die Antwort nicht in 250ms passiert, ist etwas nicht in Ordnung.

Jedoch wird das Signal nie gefangen, und ich kann nicht sicher sagen, wenn es sogar ausgestrahlt wird. Aber ich habe bemerkt, dass ich die Ereignisschleife nirgendwo im Testprojekt starte. Im Entwicklungsprojekt wird die Ereignisschleife im Wesentlichen mit QCoreApplication::exec() gestartet.


Um es zusammenzufassen, wenn das Gerät eine Klasse zu testen, die auf Signale und Slots abhängt, wo sollte die

QCoreApplication a(argc, argv); 
return a.exec(); 

in der Testumgebung ausgeführt werden?

+0

In der main() Funktion? Sie können das Makro 'QTEST_MAIN (CommunicationProtocolTest)' in Ihrem Komponententest verwenden. – vahancho

+3

'qWait' dreht seine eigene Event-Schleife, das ist also kein Problem. Versuchen Sie es mit einem längeren Timeout. Im Idealfall würden Sie Ihre eigene Mock-up-Implementierung eines 'QAbstractSocket' bereitstellen, der zum Testen verwendet wird. –

+0

@vahancho Danke, das hat das Problem gelöst, jedoch beschränkt es mich, nur eine einzige Klasse pro Lauf zu testen. – TheMeaningfulEngineer

Antwort

3

Gute Frage. Hauptprobleme, die ich getroffen habe, sind (1) app müssen app.exec() immer noch am Ende, um automatisierte Builds nicht zu blockieren und (2) muss sicherstellen, dass ausstehende Ereignisse verarbeitet werden, bevor Sie sich auf das Ergebnis des Signals verlassen/Zeitschlitzanrufe.

Für (1) könnten Sie versuchen, die app.exec() in main() auskommentieren. ABER dann, wenn jemand FooWidget.exec() in ihrer Klasse, die Sie testen, hat, wird es blockieren/hängen. So etwas ist praktisch qApp zu zwingen, zu verlassen:

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; 
} 

Für (2), im Grunde Sie QApplication in finishing die Warteschlange Ereignisse zu zwingen, wenn Sie versuchen, Dinge wie QEvent s von Maus + Tastatur überprüfen habe das Ergebnis erwartet. Diese Methode ist hilfreich:

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); 
} }; 

Anwendungsbeispiel unten. "Dialog" ist eine Instanz von MyDialog. "Baz" ist eine Instanz von Baz. "Dialog" hat ein Mitglied des Typs Bar. Wenn ein Balken einen Baz auswählt, gibt er ein Signal aus; "Dialog" ist mit dem Signal verbunden und wir müssen sicherstellen, dass der zugehörige Steckplatz die Nachricht erhalten hat.

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>*/ 
} 
3

Ich weiß, das ein alter Thread ist aber, wie ich sie getroffen und als andere werden, gibt es keine Antwort und die Antwort von Peter und anderen Kommentaren vermisse immer noch den Punkt QSignalSpy zu verwenden.

Um Ihre ursprüngliche Frage zu beantworten "wo die QCoreApplication Exec-Funktion benötigt wird", ist die Antwort im Grunde genommen, ist es nicht.QTest und QSignalSpy haben das bereits eingebaut.

Was Sie wirklich in Ihrem Testfall tun müssen, ist "laufen" die vorhandene Ereignisschleife.

Sie verwenden Qt 5 Unter der Annahme: http://doc.qt.io/qt-5/qsignalspy.html#wait

So Ihr Beispiel zu modifizieren, um die Wartefunktion zu verwenden:

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); 
} 

Das sollte geben Ihnen das gewünschte Verhalten, ohne eine andere Ereignis-Schleife erstellen zu müssen.

+1

'QSignalSpy :: wait()' benötigt 'QTEST_MAIN' anstelle von' QTEST_APPLESS_MAIN', sonst bekomme ich 'QEventLoop: Kann nicht ohne QApplication benutzt werden'. – fgiraldeau

0

Ich hatte ein ähnliches Problem mit Qt::QueuedConnection (Ereignis wird automatisch eingereiht, wenn der Absender und der Empfänger zu verschiedenen Threads gehört). Ohne eine geeignete Ereignisschleife in dieser Situation wird der interne Zustand von Objekten, die von der Ereignisverarbeitung abhängig sind, nicht aktualisiert. Um eine Ereignisschleife beim Ausführen von QTest zu starten, ändern Sie das Makro QTEST_APPLESS_MAIN am Ende der Datei in QTEST_MAIN. Dann wird der Aufruf qApp->processEvents() tatsächlich Ereignisse verarbeiten, oder Sie können eine andere Ereignisschleife mit QEventLoop starten.

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 
Verwandte Themen