suma que pthread_join pthread_create pthread procesos hilos hace ejercicios ejemplos con c++ mocking googlemock

c++ - que - Esperando llamadas googlemock de otro hilo



pthread_join que hace (3)

¿Cuál será la mejor forma de escribir (google) casos de prueba utilizando un objeto simulado de google y esperar que se invoquen las definiciones EXPECT_CALL () desde otro subproceso controlado por la clase en prueba? Simplemente llamando a sleep () o similar después de activar las secuencias de llamadas no parece apropiado, ya que puede ralentizar las pruebas innecesarias y puede que no lleguen a 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 a alguien?

Aquí hay un 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 el momento):

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 prueba 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 prueba con sleep () habilitado:

[==========] 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 modo reposo (), en el mejor de los casos, sin necesidad de cambiar la clase de barra en absoluto.


Usando lambdas, podrías hacer algo como (he puesto equivalentes de impulso 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 puedes usar lambdas, imagino que podrías usar boost::bind lugar.


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

Tenga en cuenta que el mismo principio funcionará bien para cualquier otro tipo de implementació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.


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