how to test an asynchronous method that sends callbacks

11,698 views
Skip to first unread message

Pau Pachès

unread,
May 16, 2011, 4:51:04 AM5/16/11
to googletes...@googlegroups.com
Hello,
I've searched the Guides, the FAQ and the mailing list archives but have found nothing as inspiration for my problem. It is quite simple:
I have a method which is asynchronous, it starts a lengthy render operation. So it runs in its own thread.
In fact the method is called StartRenderThread.
It reports its progress through callbacks.
Basically, apart from the above method, it has a method that is called addAnalysisListener and it takes a pointer to an interface it defines.
The interface is quite simple, with 4 methods and those methods receive information about the progress of the operation.
I have written a test that calls StartRenderThread. I can also make the testing fixture implement the interface but my test method
ends by calling StartRenderThread. How I can hook up the test method and the methods which implement the callback interface?

Thanks and regards,

Pau

Nicolás Brailovsky

unread,
May 16, 2011, 3:01:52 PM5/16/11
to Pau Pachès, googletes...@googlegroups.com
You can create a mock object implementing your listener interface, and
then subscribe this object to the object generating events, then you
can use the normal methods like Times or WillOnce.


Nicolás Brailovsky
 - Tech blog http://nicolasb.com.ar/
 - Blog personal http://monosinfinitos.com.ar/

Pau Pachès

unread,
May 18, 2011, 5:58:51 AM5/18/11
to Nicolás Brailovsky, googletes...@googlegroups.com
I've done what you describe but the basic problem is the test method exits before the
thread has completed.
In the code:
TEST_F(RenderTest, TestRender)
{
    MockIListener mockListener;
    m_pAppData->addListener(&mockListener);
    {
        InSequence dummy;
        EXPECT_CALL(mockListener, onStartRender(_,_)).Times(1);
        EXPECT_CALL(mockListener, onProgress(_,_)).Times(AtLeast(99));
        EXPECT_CALL(mockListener, onJobRendered(_,_)).Times(1);
        EXPECT_CALL(mockListener, onQueueDepleted()).Times(1);
    }

    // ... build and init render job

    m_pAppData->AddRenderJob(renderJob);   
    m_pAppData->StartRenderThread();
}

The trouble is StartRenderThread starts a thread and the method exits afterwards. So none
of the expected calls of the mock listener takes place.
The fact that the method dies without waiting for the thread to complete is not-surprising behaviour from gtest / gmock.
I guess I should change the prototype of the method and return the thread handle and wait for it
to complete in the test method.


2011/5/16 Nicolás Brailovsky <nicola...@gmail.com>

Stepan Davidovic

unread,
May 18, 2011, 6:11:08 AM5/18/11
to Pau Pachès, Nicolás Brailovsky, googletes...@googlegroups.com
Hi,

if I understand your problem correctly, a solution for you might be to set up some blocking synchronization mechanism (e.g. barrier), after you run StartRenderThread(). Then, when you get the last expected callback (which seems to be onQueueDepleted in the code you've posted), invoke an action which will unblock the synchronization so that the main thread (which invoked rendering thread) can finally finish. That can conveniently be done by creating your own actions for the EXPECT_CALL to trigger.

Hope this helps,
 - Steve

Pau Pachès

unread,
May 18, 2011, 10:37:01 AM5/18/11
to Stepan Davidovic, Nicolás Brailovsky, googletes...@googlegroups.com
Yes, you're right. Thanks Steve.
The solution was to make the test method wait until StartRenderThread has completed.
I'm working in Windows and the way I did it (may be dirty code but works...)
is as follows:
added this after the call to StartRenderThread
BOOL res = SleepConditionVariableCS(&OperationNotReady, &OperationLock, INFINITE);    EXPECT_EQ(TRUE, res);

where OperationNotReady and OperationLock are
CONDITION_VARIABLE OperationNotReady;
CRITICAL_SECTION   OperationLock;

that you must initialise properly before calling the above Sleep method.

I modified the last expect call to
EXPECT_CALL(mockListener, onQueueDepleted()).Times(1).WillOnce(Invoke(WaitFinished));

where WaitFinished is:
void WaitFinished()
{
    WakeConditionVariable(&OperationNotReady);
}

The good thing about your proposal is that I did not need to modify the interface to make my tests.
Thank you Nicolás for pointing me in gmock direction, I had started with gtest and planned to also
add gmock stuff but your hint made me add gmock earlier and find the solution earlier.

cheers.



2011/5/18 Stepan Davidovic <ste...@google.com>
Reply all
Reply to author
Forward
0 new messages