Search code examples
c++qtunit-testingqt5qtimer

QTimer timout doesnt trigger in unit testing


I'm trying to create a unit testing for a class i made named Counter. Such class should decrease its value "count" every 0.1 second, so I connected the method in the class constructor. Everything seems to work just fine in the original program, but when I try to run the unit tester it seems as the connection didnt happen or the QTimer doesnt send the timout signal at all.

gitHub link: https://github.com/allocro/TimerLFdP/tree/master/testCounter

Counter::Counter(QLineEdit *editSec, QLineEdit *editMin, QObject *parent) : 
QObject(parent)
{
    timer= new QTimer(this);
    [...]
    connect(timer, &QTimer::timeout, this, &Counter::decCount);
}

EDIT: K, I'm gonna try to fit other code part in here as I'm being asked to do so.

In the precedend code, here is the constructor of the class I want to test, what decCount() doesis nothing else than that:

void Counter::decCount(){
    if (count>0)
        count--;
    else
        timer->stop();

    int countsec = count % 600;
    int countmin = (count - countsec)/600;

    QString secOut = QString::number(countsec/10) + "." 
    +QString::number(countsec%10);

    emit showCountMin(countmin);
    emit showCountSec(secOut);
} 

The above signals are irrelevant to the unit tester code, they were used in the complete program.

Now, in the tst_testCounter.cpp, that as far as I understood, is the test unit version of the classic main, and I got

testCounter::testCounter() : QObject ()
{   int i;
    editSec= new QLineEdit();
    editMin= new QLineEdit();
    counter= new Counter(editSec, editMin);
    for(i=0; i<=10; i++){
        int randsec= rand() %60;
        int randmin= rand() %60;
        randsec=3;
        randmin=0; //To test the tester faster

        QString randsecString= QString::number(randsec);
        QString randminString= QString::number(randmin);


        editSec->setText(randsecString);
        editMin->setText(randminString);

        counter->set();//those are to input, they work fine, I checked
        std::cout << "Starting the Timer at " << (counter->count)/10 << 
        "sec" <<std::endl;
        counter->start(); //{timer->start();}

        while(counter->count>0){
            std::cout <<counter->count <<std::endl;
        }
        auto end= std::chrono::system_clock::now();
        counter->stop();

        auto check= std::chrono::duration_cast<std::chrono::seconds>(end - 
        start);
        int checkInt= check.count();

        if(checkInt==randsec+(randmin*60)){
            std::cout << "Timer matches" <<std::endl;
        }else{
        std::cout << "Timer doesnt match" <<std::endl;
        std::cout << "Sec counted: " << (counter->count)/10 <<std::endl;
        std::cout << "Sec passed: " << checkInt <<std::endl;
        break;
        }
    }
}

And obviously the .cpp ends in

QTEST_MAIN(testCounter)

#include "tst_testcounter.moc"

Solution

  • The first thing you should keep in mind is that QTimer is not exact, so doing a check in the order of seconds will cause problems, a better test verifies that the time difference in ms is less than a certain value.

    On the other hand with the instruction:

    while (counter-> count> 0) {
        std::cout << counter-> count << std::endl;
    }
    

    You are not letting the event loop work and therefore the QTimer or signals can work.

    What you should use is QCoreApplication::processEvents():

    while(counter.count>0){
        QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents);
    }
    

    It is also recommended that the elements of the test be defined within the methods, and the test is not given in the constructor but in the slots.

    Considering the above and giving other improvements, the following is obtained:

    counter.h

    #ifndef COUNTER_H
    #define COUNTER_H
    
    #include <QObject>
    class QTimer;
    class QLineEdit;
    
    class Counter : public QObject
    {
        Q_OBJECT
    public:
        explicit Counter(QLineEdit *setterSec, QLineEdit *setterMin, QObject *parent = nullptr);
        QTimer *timer;
        QLineEdit *setterSec;
        QLineEdit *setterMin;
        int count;
    signals:
        void showCountSec(const QString &count);
        void showCountMin(int count);
    
    public slots:
        void decCount();
        void start();
        void stop();
        void set();
    };
    
    #endif // COUNTER_H
    

    counter.cpp

    #include "counter.h"
    #include <QTimer>
    #include <QIntValidator>
    #include <QLineEdit>
    
    Counter::Counter(QLineEdit *editSec, QLineEdit *editMin, QObject *parent) :
        QObject(parent),
        timer(new QTimer(this)),
        setterSec(editSec),
        setterMin(editMin),
        count(0)
    {
        setterSec->setValidator(new QIntValidator(0, 59, this));
        setterMin->setValidator(new QIntValidator(0, 59, this));
        connect(timer, &QTimer::timeout, this, &Counter::decCount);
    }
    
    void Counter::start(){
        timer->start(100);
    }
    
    void Counter::stop(){
        timer->stop();
    }
    
    void Counter::set(){
        timer->stop();
        count = setterSec->text().toInt()*10 + setterMin->text().toInt()*600;
        int countsec = count % 600;
        int countmin = (count - countsec)/600;
    
        QString secOut = QString::number(countsec/10) + "." +QString::number(countsec%10);
    
        emit showCountMin(countmin);
        emit showCountSec(secOut);
    }
    
    void Counter::decCount(){
        if (count > 0)
                count--;
        else
            timer->stop();
    
        int countsec = count % 600;
        int countmin = (count - countsec)/600;
    
        QString secOut = QString::number(countsec/10) + "." +QString::number(countsec%10);
    
        emit showCountMin(countmin);
        emit showCountSec(secOut);
    }
    

    tst_counter.h

    #include <QtTest>
    #include "counter.h"
    #include <QLineEdit>
    #include <iostream>
    #include <cmath>
    #include <QDebug>
    
    // add necessary includes here
    
    class tst_Counter : public QObject
    {
        Q_OBJECT
    public:
        tst_Counter();
        ~tst_Counter();
    
    private slots:
        void initTestCD();
    };
    
    tst_Counter::tst_Counter() : QObject ()
    {
    }
    tst_Counter::~tst_Counter()
    {
    
    }
    
    void tst_Counter::initTestCD(){
        QLineEdit editSec;
        QLineEdit editMin;
        Counter counter(&editSec, &editMin);
        const int epsilon = 50; // ms
    
        for(int i=0; i<=10; i++){
            int randsec= rand() %60;
            int randmin= rand() %60;
            randsec =3;
            randmin =0; //To test the tester faster
    
            QString randsecString= QString::number(randsec);
            QString randminString= QString::number(randmin);
    
    
            editSec.setText(randsecString);
            editMin.setText(randminString);
    
            counter.set();//those are to input, they work fine, I checked
            qDebug() << "Starting the Timer at " << (counter.count)/10;
    
            auto start= std::chrono::system_clock::now();
            counter.start(); //{timer->start();}
            while(counter.count>0){
                QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents);
            }
            auto end= std::chrono::system_clock::now();
            counter.stop();
            auto check_ms= std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
            int checkInt = check_ms.count();
            int delta_s = randsec + randmin*60;
            int delta = std::abs(checkInt - delta_s);
            QVERIFY(delta < epsilon);
    
            qDebug() << "Sec counted: " << counter.count/10;
            qDebug() << "msec passed: " << checkInt;
        }
    }
    
    QTEST_MAIN(tst_Counter)
    
    #include "tst_counter.moc"
    

    output:

    ********* Start testing of tst_Counter *********
    Config: Using QtTest library 5.11.2, Qt 5.11.2 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 8.2.1 20180831)
    PASS   : tst_Counter::initTestCase()
    QDEBUG : tst_Counter::initTestCD() Starting the Timer at  3
    QDEBUG : tst_Counter::initTestCD() Sec counted:  0
    QDEBUG : tst_Counter::initTestCD() msec passed:  3033
    QDEBUG : tst_Counter::initTestCD() Starting the Timer at  3
    QDEBUG : tst_Counter::initTestCD() Sec counted:  0
    QDEBUG : tst_Counter::initTestCD() msec passed:  2999
    QDEBUG : tst_Counter::initTestCD() Starting the Timer at  3
    QDEBUG : tst_Counter::initTestCD() Sec counted:  0
    QDEBUG : tst_Counter::initTestCD() msec passed:  3000
    QDEBUG : tst_Counter::initTestCD() Starting the Timer at  3
    QDEBUG : tst_Counter::initTestCD() Sec counted:  0
    QDEBUG : tst_Counter::initTestCD() msec passed:  2999
    QDEBUG : tst_Counter::initTestCD() Starting the Timer at  3
    QDEBUG : tst_Counter::initTestCD() Sec counted:  0
    QDEBUG : tst_Counter::initTestCD() msec passed:  2999
    QDEBUG : tst_Counter::initTestCD() Starting the Timer at  3
    QDEBUG : tst_Counter::initTestCD() Sec counted:  0
    QDEBUG : tst_Counter::initTestCD() msec passed:  3000
    QDEBUG : tst_Counter::initTestCD() Starting the Timer at  3
    QDEBUG : tst_Counter::initTestCD() Sec counted:  0
    QDEBUG : tst_Counter::initTestCD() msec passed:  2999
    QDEBUG : tst_Counter::initTestCD() Starting the Timer at  3
    QDEBUG : tst_Counter::initTestCD() Sec counted:  0
    QDEBUG : tst_Counter::initTestCD() msec passed:  2999
    QDEBUG : tst_Counter::initTestCD() Starting the Timer at  3
    QDEBUG : tst_Counter::initTestCD() Sec counted:  0
    QDEBUG : tst_Counter::initTestCD() msec passed:  2999
    QDEBUG : tst_Counter::initTestCD() Starting the Timer at  3
    QDEBUG : tst_Counter::initTestCD() Sec counted:  0
    QDEBUG : tst_Counter::initTestCD() msec passed:  2999
    QDEBUG : tst_Counter::initTestCD() Starting the Timer at  3
    QDEBUG : tst_Counter::initTestCD() Sec counted:  0
    QDEBUG : tst_Counter::initTestCD() msec passed:  2999
    PASS   : tst_Counter::initTestCD()
    PASS   : tst_Counter::cleanupTestCase()
    Totals: 3 passed, 0 failed, 0 skipped, 0 blacklisted, 33078ms
    ********* Finished testing of tst_Counter *********
    

    In the following link you can find the complete example.