2012-05-26 15 views
9

¿Cuál será la mejor manera de escribir (google) casos de prueba utilizando un objeto simulado google y esperar que las definiciones EXPECT_CALL() se llamen desde otro hilo controlado por la clase en prueba? Simplemente llamando a sleep() o similar después de activar las secuencias de llamadas no se considera apropiado, ya que puede ralentizar las pruebas innecesarias y puede que no afecten realmente las condiciones de tiempo. Pero terminar el caso de prueba de alguna manera tiene que esperar hasta que se invoquen los métodos simulados. Ideas anyone?Esperando llamadas googlemock de otro hilo

Aquí hay algo de código para ilustrar la situación:

Bar.hpp (la clase bajo prueba)

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 (extracto):

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); 
    } 
} 

El código de prueba (extracto, nada especial en la definición de FooInterfaceMock hasta ahora):

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(); 
} 

resultados de las pruebas sin dormir():

[==========] 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 

Resultados de las pruebas con el sueño() activados:

[==========] 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. 

quiero evitar el sueño(), en el mejor de los casos, sin necesidad de cambiar la barra de clase en absoluto.

Respuesta

12

La respuesta de Fraser me inspiró para una solución simple usando una acción especializada de GMock. GMock hace que sea muy fácil escribir rápidamente tales acciones.

Aquí está el código (extracto de 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 el mismo principio funciona bien para cualquier otro tipo de aplicación de semáforos como boost::interprocess::interprocess_semaphore. Lo estoy usando para probar con nuestro código de producción que usa su propia capa de abstracción del sistema operativo y la implementación de semáforos.

+0

El 'timed_wait()' no funcionará correctamente si usa 'local_time()' al calcular 'until'. Deberías usar 'universal_time()' en su lugar. – Rom098

+0

@ Rom098 THX para la pista.El ejemplo real que tengo usa nuestro propio OSAL, puse las funciones de impulso aquí como un sustituto conciso. –

5

Uso de lambdas, usted podría hacer algo así (he puesto equivalentes a impulsar en los comentarios):

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(); 
} 

Si no puede utilizar lambdas, imagino que usted podría utilizar en lugar boost::bind.

+0

Hola Fraser, muchas gracias por tu respuesta. Lamentablemente, en el entorno en el que quiero escribir las pruebas, finalmente no tengo ni lambdas, ni boost disponibles (he estado usando boost solo para escribir rápidamente una demostración del problema). Sin embargo, su respuesta me inspiró para una solución simple utilizando una acción GMock especializada (tipo de sustituto de una lambda). –

0

La respuesta de Fraser también me inspiró. Usé su sugerencia, y funcionó, pero luego encontré otra forma de lograr lo mismo sin la variable de condición. Tendrá que agregar un método para verificar alguna condición, y necesitará un ciclo infinito. Esto también supone que tienes un hilo separado que actualizará la condición.

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(); 
} 
Cuestiones relacionadas