web-dev-qa-db-ja.com

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

私の動機は、250ms以内に応答が起こらなければ、何かがおかしいということでした。

しかし、信号がキャッチされることはなく、信号が発信されているかどうかはわかりません。しかし、テストプロジェクトのどこでもイベントループを開始していないことに気づきました。開発プロジェクトでは、イベントループはメインでQCoreApplication::exec()で開始されます。


要約すると、信号とスロットに依存するクラスをユニットテストする場合、どこで

_QCoreApplication a(argc, argv);
return a.exec();
_

テスト環境で実行されますか?

23

これは古いスレッドだと思いますが、私がそれを打ったとき、そして他の人がそうするように、答えはなく、ピーターや他のコメントによる答えはまだQSignalSpyを使用するポイントを逃しています。

「QCoreApplicationexec関数が必要な場所」に関する最初の質問に答えるには、基本的に答えはそうではありません。 QTestとQSignalSpyにはすでにそれが組み込まれています。

テストケースで実際に行う必要があるのは、既存のイベントループを「実行」することです。

Qt 5を使用していると仮定します: http://doc.qt.io/qt-5/qsignalspy.html#wait

したがって、待機関数を使用するように例を変更するには、次のようにします。

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

これにより、別のイベントループを作成しなくても、目的の動作が得られるはずです。

10
Dan Hogan

良い質問。私が直面した主な問題は、(1)自動ビルドをブロックしないようにアプリにapp.exec()を実行させる必要があるが、(2)シグナルの結果に依存する前に、保留中のイベントが処理されるようにする必要があることです。/slot呼び出し。

(1)の場合、main()でapp.exec()をコメントアウトしてみてください。 [〜#〜] but [〜#〜]次に、テストしているクラスに誰かが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)の場合、マウス+キーボードからのQEventsのようなものが期待される結果を持っていることを確認しようとすると、基本的にQApplicationにキューに入れられたイベントを終了させる必要があります。この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のインスタンスです。 「baz」はBazのインスタンスです。 「ダイアログ」にはタイプ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>*/
}
_
6
peter karasev

_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
_
1
fgiraldeau