Search code examples
c++qtunit-testingparallel-processingqthread

Unit test tasks run in parallel using QThreadPool


I am using QThreadPool to run tasks in my application in parallel. The tasks take the thread pool as an argument, so they can start new tasks. How can I write unit tests for the tasks and assert that the correct next tasks are started?

class MyTask: public QRunnable {
public:
    virtual void run() {
        m_threadPool.start(/*another task*/);
        m_threadPool.start(/*a third task*/);
    }
private:
    QThreadPool &m_threadPool;
};

I would like to test MyTask:

QThreadPool threadPool;
threadPool.start(new MyTask(threadPool));
threadPool.waitForDone();

// Assert that another task and a third task is started.

I tried extending QThreadPool and log started tasks:

class MyThreadPool : public QThreadPool {
public:
    virtual void start(QRunnable *runnable, int priority = 0) {
        m_Queue.enqueue(runnable);
        // The task is not started, so I can manually start each task and test the outcome.
    }
    QQueue<QRunnable *> queue() const { return m_queue; }
private:
    QQueue<QRunnable *> m_queue;
};


MyThreadPool threadPool;
threadPool.start(new MyTask(threadPool));
threadPool.waitForDone();

QQueue<QRunnable *> Queue({ /*another task and a third task*/ });
Assert::IsEquavalent(threadPool.queue(), Queue);

But this does not work, since QThreadPool::start() is not virtual. What is the best approach for writing my test?


Solution

  • Regarding the problems about the fact that QThreadPool::start() is not a virtual function, you can do something like this:

    Instead of overriding a function, you can use your subclass in MyTask and use a different function that will call run. Something like this:

    class MyThreadPool : public QThreadPool {
    public:
        void enqueue_and_run(QRunnable *runnable, int priority = 0) {
            m_Queue.enqueue(runnable);
            QThreadPool::start(runnable, priority);
        }
    
        const QQueue<QRunnable *>& queue() const { 
             return m_queue; 
        }
    private:
        QQueue<QRunnable *> m_queue;
    };
    
    class MyTask: public QRunnable {
    public:
        virtual void run() {
            m_threadPool.enqueue_and_run(/*another task*/);
            m_threadPool.enqueue_and_run(/*a third task*/);
        }
    private:
        MyThreadPool &m_threadPool;
    };
    

    THen run the same test code:

    MyThreadPool threadPool;
    threadPool.enqueue_and_run(new MyTask(threadPool));
    threadPool.waitForDone();
    
    QQueue<QRunnable *> Queue({ /*another task and a third task*/ });
    Assert::IsEquavalent(threadPool.queue(), Queue);
    

    It is not the most elegant way to do so, but it makes clear your intentions.

    As an alternative, if you want to keep a common interface, you can use a basic function overloading:

    template <class TPool>
    void start(TPool* pool, QRunnable *runnable, int priority = 0) {
        pool->start(runnable, priority);
    }
    
    void start(MyThreadPool* pool, QRunnable *runnable, int priority = 0) {
        pool->enqueue_and_run(pool, runnable, priority);
    }  
    

    Then your test code will work pretty similar to the original one:

    MyThreadPool threadPool;
    start(threadPool, new MyTask(threadPool));
    threadPool.waitForDone();
    // ... rest of the code