2012-05-26 5 views
9

Quale sarà il modo migliore per scrivere casi di test (google) utilizzando un oggetto google mock e aspettarsi che le definizioni EXPECT_CALL() vengano chiamate da un altro thread controllato dalla classe in test? Il semplice richiamo di sleep() o simili dopo l'attivazione delle sequenze di chiamata non sembra appropriato in quanto potrebbe rallentare il test non necessario e potrebbe non raggiungere le condizioni di temporizzazione. Ma finire il test case in qualche modo deve aspettare fino a quando non sono stati chiamati i metodi simulati. Idee qualcuno?Aspettarsi chiamate googlemock da un altro thread

Ecco po 'di codice per illustrare la situazione:

Bar.hpp (la classe in prova)

class Bar 
{ 
public: 

Bar(IFooInterface* argFooInterface); 
virtual ~Bar(); 

void triggerDoSomething(); 
void start(); 
void stop(); 

private: 
void* barThreadMethod(void* userArgs); 
void endThread(); 
void doSomething(); 

ClassMethodThread<Bar> thread; // A simple class method thread implementation using boost::thread 
IFooInterface* fooInterface; 
boost::interprocess::interprocess_semaphore semActionTrigger; 
boost::interprocess::interprocess_semaphore semEndThread; 
bool stopped; 
bool endThreadRequested; 
}; 

Bar.cpp (estratto):

void Bar::triggerDoSomething() 
{ 
    semActionTrigger.post(); 
} 

void* Bar::barThreadMethod(void* userArgs) 
{ 
    (void)userArgs; 
    stopped = false; 
    do 
    { 
     semActionTrigger.wait(); 
     if(!endThreadRequested && !semActionTrigger.try_wait()) 
     { 
      doSomething(); 
     } 
    } while(!endThreadRequested && !semEndThread.try_wait()); 
    stopped = true; 
    return NULL; 
} 

void Bar::doSomething() 
{ 
    if(fooInterface) 
    { 
     fooInterface->func1(); 
     if(fooInterface->func2() > 0) 
     { 
      return; 
     } 
     fooInterface->func3(5); 
    } 
} 

Il codice di test (estratto, niente di speciale nella definizione di FooInterfaceMock finora):

class BarTest : public ::testing::Test 
{ 
public: 

    BarTest() 
    : fooInterfaceMock() 
    , bar(&fooInterfaceMock) 
    { 
    } 

protected: 
    FooInterfaceMock fooInterfaceMock; 
    Bar bar; 
}; 

TEST_F(BarTest, DoSomethingWhenFunc2Gt0) 
{ 
    EXPECT_CALL(fooInterfaceMock,func1()) 
     .Times(1); 
    EXPECT_CALL(fooInterfaceMock,func2()) 
     .Times(1) 
     .WillOnce(Return(1)); 

    bar.start(); 
    bar.triggerDoSomething(); 
    //sleep(1); 
    bar.stop(); 
} 

I risultati dei test senza sonno():

[==========] Running 1 test from 1 test case. 
[----------] Global test environment set-up. 
[----------] 1 test from BarTest 
[ RUN  ] BarTest.DoSomethingWhenFunc2Gt0 
../test/BarTest.cpp:39: Failure 
Actual function call count doesn't match EXPECT_CALL(fooInterfaceMock, func2())... 
     Expected: to be called once 
      Actual: never called - unsatisfied and active 
../test/BarTest.cpp:37: Failure 
Actual function call count doesn't match EXPECT_CALL(fooInterfaceMock, func1())... 
     Expected: to be called once 
      Actual: never called - unsatisfied and active 
[ FAILED ] BarTest.DoSomethingWhenFunc2Gt0 (1 ms) 
[----------] 1 test from BarTest (1 ms total) 

[----------] Global test environment tear-down 
[==========] 1 test from 1 test case ran. (1 ms total) 
[ PASSED ] 0 tests. 
[ FAILED ] 1 test, listed below: 
[ FAILED ] BarTest.DoSomethingWhenFunc2Gt0 

1 FAILED TEST 
terminate called after throwing an instance of   'boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::lock_error> >' 
Aborted 

risultati dei test con il sonno() abilitati:

[==========] Running 1 test from 1 test case. 
[----------] Global test environment set-up. 
[----------] 1 test from BarTest 
[ RUN  ] BarTest.DoSomethingWhenFunc2Gt0 
[  OK ] BarTest.DoSomethingWhenFunc2Gt0 (1000 ms) 
[----------] 1 test from BarTest (1000 ms total) 

[----------] Global test environment tear-down 
[==========] 1 test from 1 test case ran. (1000 ms total) 
[ PASSED ] 1 test. 

voglio evitare il sonno(), nel migliore dei casi, senza necessità di cambiare la barra classe a tutti.

risposta

12

La risposta di Fraser mi ha ispirato per una soluzione semplice utilizzando un'azione specializzata GMock. GMock rende molto facile scrivere rapidamente tali azioni.

Ecco il codice (estratto da BarTest.cpp):

// Specialize an action that synchronizes with the calling thread 
ACTION_P2(ReturnFromAsyncCall,RetVal,SemDone) 
{ 
    SemDone->post(); 
    return RetVal; 
} 

TEST_F(BarTest, DoSomethingWhenFunc2Gt0) 
{ 
    boost::interprocess::interprocess_semaphore semDone(0); 
    EXPECT_CALL(fooInterfaceMock,func1()) 
     .Times(1); 
    EXPECT_CALL(fooInterfaceMock,func2()) 
     .Times(1) 
     // Note that the return type doesn't need to be explicitly specialized 
     .WillOnce(ReturnFromAsyncCall(1,&semDone)); 

    bar.start(); 
    bar.triggerDoSomething(); 
    boost::posix_time::ptime until = boost::posix_time::second_clock::universal_time() + 
      boost::posix_time::seconds(1); 
    EXPECT_TRUE(semDone.timed_wait(until)); 
    bar.stop(); 
} 

TEST_F(BarTest, DoSomethingWhenFunc2Eq0) 
{ 
    boost::interprocess::interprocess_semaphore semDone(0); 
    EXPECT_CALL(fooInterfaceMock,func1()) 
     .Times(1); 
    EXPECT_CALL(fooInterfaceMock,func2()) 
     .Times(1) 
     .WillOnce(Return(0)); 
    EXPECT_CALL(fooInterfaceMock,func3(Eq(5))) 
     .Times(1) 
     // Note that the return type doesn't need to be explicitly specialized 
     .WillOnce(ReturnFromAsyncCall(true,&semDone)); 

    bar.start(); 
    bar.triggerDoSomething(); 
    boost::posix_time::ptime until = boost::posix_time::second_clock::universal_time() + 
      boost::posix_time::seconds(1); 
    EXPECT_TRUE(semDone.timed_wait(until)); 
    bar.stop(); 
} 

nota lo stesso principio funziona bene per qualsiasi altro tipo di implementazione del semaforo come boost::interprocess::interprocess_semaphore. Lo sto usando per testare con il nostro codice di produzione che utilizza il proprio livello di astrazione del sistema operativo e l'implementazione del semaforo.

+0

'timed_wait()' non funzionerà correttamente se si utilizza 'local_time()' durante il calcolo di 'until'. Dovresti usare 'universal_time()'. – Rom098

+0

@ Rom098 THX per il suggerimento.Il vero esempio che ho usato il nostro OSAL personale, ho messo le funzioni di boost qui come un surrogato conciso. –

5

Utilizzando lambda, si potrebbe fare qualcosa di simile (ho messo equivalenti Boost nei commenti):

TEST_F(BarTest, DoSomethingWhenFunc2Gt0) 
{ 
    std::mutex mutex;     // boost::mutex mutex; 
    std::condition_variable cond_var; // boost::condition_variable cond_var; 
    bool done(false); 

    EXPECT_CALL(fooInterfaceMock, func1()) 
     .Times(1); 
    EXPECT_CALL(fooInterfaceMock, func2()) 
     .Times(1) 
     .WillOnce(testing::Invoke([&]()->int { 
      std::lock_guard<std::mutex> lock(mutex); // boost::mutex::scoped_lock lock(mutex); 
      done = true; 
      cond_var.notify_one(); 
      return 1; })); 

    bar.start(); 
    bar.triggerDoSomething(); 
    { 
     std::unique_lock<std::mutex> lock(mutex);    // boost::mutex::scoped_lock lock(mutex); 
     EXPECT_TRUE(cond_var.wait_for(lock,      // cond_var.timed_wait 
            std::chrono::seconds(1), // boost::posix_time::seconds(1), 
            [&done] { return done; })); 
    } 
    bar.stop(); 
} 

Se non è possibile utilizzare lambda, immagino si potrebbe usare boost::bind invece.

+0

Ciao Fraser, grazie mille per la risposta. Sfortunatamente nell'ambiente in cui voglio scrivere i test finalmente non ho né lambdas, né boost disponibile (ho usato boost solo per scrivere rapidamente una demo del problema). Tuttavia la tua risposta mi ha ispirato per una soluzione semplice usando un'azione GMock specializzata (tipo di surrogato per un lambda). –

0

La risposta di Fraser mi ha ispirato anche. Ho usato il suo suggerimento, e ha funzionato, ma poi ho trovato un altro modo per realizzare lo stesso senza la variabile di condizione. Dovrai aggiungere un metodo per verificare alcune condizioni e avrai bisogno di un ciclo infinito. Ciò presuppone anche che tu disponga di un thread separato che aggiornerà la condizione.

TEST_F(BarTest, DoSomethingWhenFunc2Gt0) 
{ 
    EXPECT_CALL(fooInterfaceMock,func1()).Times(1); 
    EXPECT_CALL(fooInterfaceMock,func2()).Times(1).WillOnce(Return(1)); 

    bar.start(); 
    bar.triggerDoSomething(); 

    // How long of a wait is too long? 
    auto now = chrono::system_clock::now(); 
    auto tooLong = now + std::chrono::milliseconds(50); 

    /* Expect your thread to update this condition, so execution will continue 
    * as soon as the condition is updated and you won't have to sleep 
    * for the remainder of the time 
    */ 
    while (!bar.condition() && (now = chrono::system_clock::now()) < tooLong) 
    { 
     /* Not necessary in all cases, but some compilers may optimize out 
     * the while loop if there's no loop body. 
     */ 
     this_thread::sleep_for(chrono::milliseconds(1)); 
    } 

    // If the assertion fails, then time ran out. 
    ASSERT_LT(now, tooLong); 

    bar.stop(); 
}