QtTest Testing Class with delayed event
-
I try to get into the unit test framework from Qt. So i thought it would be a good idea to write tests for the minesweeper clone i wrote.
However i ran into trouble getting one test to pass:
I wanto test in my
Cell
Class if the Signalflagged
is emitted:class Cell : public QWidget { Q_OBJECT public: //.... void handleMousePressEvent(QMouseEvent *event); //.... signals: void flagged(); //.... };
Flagged is normally emitted if the User does a right click on the widget, after it was constructed.
I tryed to test it like this:
void TestCell::flagged() { QFETCH(Cell::State, cellState); QFETCH(int, result); Cell obj{ cellState }; QSignalSpy spy(&obj, &Cell::flagged); pressRightMouseButton(obj); // wait here until really counts as pressed right? QCOMPARE(spy.count(), result); }
The strange
pressRightMouseButton
function does this:void pressRightMouseButton(Cell &cell) { QMouseEvent pressRightButton{ QEvent::MouseButtonPress, cell.rect().center(), Qt::RightButton, Qt::RightButton, Qt::NoModifier }; cell.handleMousePressEvent(&pressRightButton); }
handleMousePressEvent
is supposed to be used from a Event Filter class which gets installed in all the Cells. So i simulate its call here. This is to handle other actions like holding both mouse buttons down and slide from one widget into annother.However now to my main problem. The flagged signal is normally emitted like this in the
Cell
class:void Cell::handleMousePressEvent(QMouseEvent *event) { if(!(event->buttons().testFlag(Qt::LeftButton) || event->buttons().testFlag(Qt::RightButton))) { return; } const auto elapsedTime = mElapsedTimer.restart(); if(elapsedTime < QApplication::doubleClickInterval()){ if(event->buttons().testFlag(Qt::LeftButton) && event->buttons().testFlag(Qt::RightButton)) { if(!isPressed()) { pressIfReleased(); mNeighboursPressed = true; emit pressNeighbours(); } } for(QTimer* timer : { &mSingleMouseTimerRight, &mSingleMouseTimerLeft }) { timer->stop(); } return; } if(event->buttons().testFlag(Qt::LeftButton)) { mSingleMouseTimerLeft.start(); } else { mSingleMouseTimerRight.start(); } }
In the constructor:
constexpr auto intervall = 50; for(QTimer* timer : {&mSingleMouseTimerRight, &mSingleMouseTimerLeft}){ timer->setInterval(intervall); timer->setSingleShot(true); } connect(&mSingleMouseTimerLeft, &QTimer::timeout, this, &Cell::leftMouseButtonWasClicked); connect(&mSingleMouseTimerRight, &QTimer::timeout, this, &Cell::rightMouseButtonWasClicked);
So if 50ms pass it counts as right of left clicked and this is called:
void Cell::rightMouseButtonWasClicked() { mark(); }
void Cell::mark() { switch (mDisplayType) { case DisplayType::covered: mDisplayType = DisplayType::flagged; emit flagged(); update(); break; case DisplayType::flagged: if(mQuestionMarksOn) { mDisplayType = DisplayType::questionmark; } else { mDisplayType = DisplayType::covered; } emit unflagged(); update(); break; case DisplayType::questionmark: mDisplayType = DisplayType::covered; update(); break; default: break; } }
My problem is the test above seems not to pass because the 50ms delay is never reached. But how can i reach it that
rightMouseButtonWasClicked
is triggered?I tryed out
QThread::msleep()
but no result with that.If my explanation is confusing and you need more information let me know.
-
Hi,
Out of curiosity, why not use QTest::mouseClick ?
-
But that only works if i directly override
mousePressEvent
in the Cell class or doesn't it?I cannot do that because in reality the
Cell
Events get managed from aCellInputHandler
eventfilter Class which gets the events before to also detect stuff like move events from one to annother widget.That EventFilter normally calls
handleMousePressEvent
More ont this here:
https://forum.qt.io/topic/107430/detect-if-mouse-buttons-are-pressed-when-sliding-into-widget/8
So its then used like this:
bool CellInputHandler::eventFilter(QObject *watched, QEvent *event) { if(event->type() == QEvent::MouseButtonPress){ handleMouseButtonPressEvents(watched, event); return true; } if(event->type() == QEvent::MouseButtonRelease){ handleMouseButtonReleaseEvents(watched, event); return true; } if(event->type() == QEvent::MouseMove) { handleMouseMoveEvents(event); return true; } return false; } void CellInputHandler::handleMouseButtonPressEvents( QObject *watched, QEvent *event) { auto mouseEvent = static_cast<QMouseEvent*>(event); auto cell = qobject_cast<Cell *>(watched); cell->handleMousePressEvent(mouseEvent); }
-
Since you are testing a functionality that requires your custom filter, why not install it as part of your test ?
It would match closer the real situation, no ? -
Yes i guess you are right. I was thinking of making
CellInputHandler
a Singleton because the same Handler needs to be installed on allCells
. That way i could maybe install it in the constructor ofCell
or is that not possible?Currently the parent of all
Cells
holds the Handler and installs it on all theCells
it owns aswell. -
Your current situation is cleaner. There's no need for a singleton.
-
Ok so i adjusted my tests to Test
Cell
with an installedCellInputHandler
.To Check for an click and release event there is no issue:
void TestCell::hitMine_data() { QTest::addColumn<Cell::State>("cellState"); QTest::addColumn<int>("result"); QTest::newRow("has mine") << Cell::State::mine << 1; QTest::newRow("is empty") << Cell::State::empty << 0; } void TestCell::hitMine() { QFETCH(Cell::State, cellState); QFETCH(int, result); CellInputHandler filter; Cell obj{ cellState }; obj.installEventFilter(&filter); QSignalSpy spy(&obj, &Cell::hitMine); QTest::mouseClick(&obj, Qt::LeftButton, Qt::NoModifier); QCOMPARE(spy.count(), result); }
This passes. The Widget gets clicked and released and we have the
hitMine
signal emited.However still doing the right click does not pass:
void TestCell::flagged_data() { QTest::addColumn<Cell::State>("cellState"); QTest::addColumn<int>("result"); QTest::newRow("has mine") << Cell::State::mine << 1; QTest::newRow("is empty") << Cell::State::empty << 1; } void TestCell::flagged() { QFETCH(Cell::State, cellState); QFETCH(int, result); CellInputHandler filter; Cell obj{ cellState }; obj.installEventFilter(&filter); QSignalSpy spy(&obj, &Cell::flagged); QTest::mousePress(&obj, Qt::RightButton, Qt::NoModifier); QTest::mouseRelease(&obj, Qt::RightButton, Qt::NoModifier, obj.rect().center(), 100); QCOMPARE(spy.count(), result); }
here the signal gets emitted when right was clicked and 50ms are passed (See the Description in my first post).
So what am i doing wrong here?
-
I would go with QTest::qWaitFor.
-
This post is deleted!
-
I tryed these two things but both don't work:
QTest::mousePress(&obj, Qt::RightButton, Qt::NoModifier, obj.rect().center()); QTest::qWaitFor([]() { return false; }, 3000); QTest::mouseRelease(&obj, Qt::RightButton, Qt::NoModifier, obj.rect().center(), 100); QCOMPARE(spy.count(), result);
and
QTest::mousePress(&obj, Qt::RightButton, Qt::NoModifier, obj.rect().center()); QTest::mouseRelease(&obj, Qt::RightButton, Qt::NoModifier, obj.rect().center(), 100); QTest::qWaitFor([&]() { return spy.count() == 1; }, 3000); QCOMPARE(spy.count(), result);
-
i solved the issue. I found out that only when I add two press statements with a delay after the second the method works correctly.
I fixed the bug like this:
void Cell::handleMousePressEvent(QMouseEvent *event) { if(!(event->buttons().testFlag(Qt::LeftButton) || event->buttons().testFlag(Qt::RightButton))) { return; } if(event->buttons().testFlag(Qt::LeftButton)) { mSingleMouseTimerLeft.start(); } else if (event->buttons().testFlag(Qt::RightButton)){ mSingleMouseTimerRight.start(); } const auto elapsedTime = mElapsedTimer.restart(); if(elapsedTime < QApplication::doubleClickInterval() && event->buttons().testFlag(Qt::LeftButton) && event->buttons().testFlag(Qt::RightButton)) { if(!isPressed()) { pressIfReleased(); mNeighboursPressed = true; emit pressNeighbours(); } for(QTimer* timer : { &mSingleMouseTimerRight, &mSingleMouseTimerLeft }) { timer->stop(); } return; } }
Before we always slided into
elapsedTime < QApplication::doubleClickInterval
and then the first time:for(QTimer* timer : { &mSingleMouseTimerRight, &mSingleMouseTimerLeft }) { timer->stop(); }
was triggered erasing the passed time. only with two nice delays i could get it to work. Now after fix it works as expected:
void TestCell::flagged() { constexpr auto delayClickRightAndLeftTogether = 50; QFETCH(Cell::State, cellState); QFETCH(int, result); CellInputHandler filter; Cell obj{ cellState }; obj.installEventFilter(&filter); QSignalSpy spy(&obj, &Cell::flagged); QTest::mousePress(&obj, Qt::RightButton, Qt::NoModifier, obj.rect().center()); spy.wait(delayClickRightAndLeftTogether); QCOMPARE(spy.count(), result); }
So i already found a bug. Thats great.
Note: I used
spy.wait
but i think its likeQTest::qWaitFor
Without condition right?