Search code examples
c++unit-testingvisual-c++mstest

Is it a good practice to ran a thread from a unit test case?


I have class which has a execute() function. Execution of execute() function only stops when terminate() function is called. I want to test the execute() function.

class Process{
public:

    void execute(){ // start execution until terminate() is called.. }

    void terminate(){ //stop the processing of execute()... }

}

My unit test case is given below. I am using MSTest.

TEST_METHOD(StartTest)
{
    Process p;
    bool isRunning = true;
    std::thread th([&](){
        p.execute();
        isRunning = false;
    });
    th.detach();
    std::this_thread::sleep_for(std::chrono::milliseconds(300));

    Assert::isTrue(isRunning);
}

If using thread a good practice should I close the thread inside the test case instead of detaching it from main thread?

Also better suggestion is appreciable.


Solution

  • First of all access to isRunning should be synchronized. In your example you can simply use std::atomic<bool> and be done with it.

    Disclaimer: it's been a while since I've done any kind of serios multithreading so take this with a grain of salt. Also, I haven't tested to code, other than to check it compiles.

    This is where I would start:

    auto test()
    {
        std::condition_variable cv{};
        std::mutex m{};
    
        Process p{};
        bool isRunning{true};
    
        std::thread th([&] {
            p.execute();        
            {
                std::lock_guard<std::mutex> lk{m};
                isRunning = false;
            }
            cv.notify_one();
        });
    
        {
            std::unique_lock<std::mutex> lk{m};
            // expect timeout
            Assert::isFalse(cv.wait_for(lk, std::chrono::milliseconds(300),
                                       [&] () { return !isRunning; }));
        }
    
        p.terminate();
    
        {
            std::unique_lock<std::mutex> lk{m};
            // expect condition to change
            Assert::isTrue(cv.wait_for(lk, std::chrono::milliseconds(300),
                                       [&] () { return !isRunning; }));
        }
    
        th.join();
    }
    

    This way you check both for execute to block and for terminate to terminate and you have more flexibility. If the execute unblocks early you don't wait your full timeout and for terminate you have a wiggle to wait for the other thread to finish and you unblock as soon as it does.


    If terminate() fails to stop the execution, will th thread continue his execution after the end of this test case?

    If terminate doesn't stop the execution then the 2nd wait_for ends after timeout returning false and the assert kicks in. I don't know what testing framework you use and what the Assert does.

    • if it returns the execution to test then the test will block on join until the thread finishes

    • if it throws an exception then the join is not called and at the destructor of th if the thread has still not ended std::terminate will be called. This can be changed with a try catch

    • if it forces an exit (e.g. calls std::terminate) then... well... your program ends regardless

    This is indeed a problem you need to analyze. It all depends on what you want to do if terminate fails to stop execute within your wait interval.

    • if you are ok with waiting within test, then all you need to do is make sure join is called. As I've said this is solvable with a try catch.

    • if you want to end the current test but are ok with the thread still going on then you need to detach the thread if terminate failed to end it.

    • if you want to kill the thread then... that's not possible. You could instead kill the entire app via std::terminate.