2012-05-26 12 views
9

Jaki będzie najlepszy sposób pisania przypadków testowych (google) za pomocą obiektu próbnego google i oczekiwanie, że definicje EXPECT_CALL() będą wywoływane z innego wątku kontrolowanego przez testowaną klasę? Samo wywołanie trybu uśpienia() lub podobne po wywołaniu sekwencji wywołań nie jest odpowiednie, ponieważ może spowolnić testowanie i niekoniecznie musi wpłynąć na warunki czasowe. Ale kończąc przypadek testowy trzeba jakoś poczekać, aż zostaną wywołane metody próbne. Czy ktoś ma pomysły?Oczekiwanie połączeń googlemock z innego wątku

Oto niektóre kodu do zilustrowania sytuacji:

Bar.hpp (klasa badanego)

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

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

Kod badania (fragment, nic specjalnego w definicji FooInterfaceMock do tej pory):

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

Wyniki badań bez snu():

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

wyników testów ze snem() włączone:

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

Chcę uniknąć sleep(), w najlepszym przypadku, bez konieczności zmiany Bar klasa w ogóle.

Odpowiedz

12

Odpowiedź Frasera zainspirowała mnie do prostego rozwiązania przy użyciu Specjalnej Akcji GMock. GMock bardzo ułatwia szybkie pisanie takich akcji.

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

Uwaga ta sama zasada będzie działać dobrze dla jakiegokolwiek innego rodzaju semafora realizacji jako boost::interprocess::interprocess_semaphore. Używam go do testowania z naszym kodem produkcyjnym, który używa własnej warstwy abstrakcji systemu operacyjnego i implementacji semaforów.

+0

'timed_wait()' nie będzie działać poprawnie, jeśli użyjesz 'local_time()' podczas obliczania 'until'. Zamiast tego należy użyć 'universal_time()'. – Rom098

+0

@ Rom098 THX za podpowiedź.Prawdziwy przykład, w którym używam własnego OSAL, umieszczam funkcje doładowania tutaj jako zwięzły surogat. –

5

Korzystanie lambdas, można zrobić coś takiego (mam umieścić odpowiedniki impuls w komentarzach):

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

Jeśli nie można używać lambdy, wyobrażam sobie, można użyć zamiast boost::bind.

+0

Cześć Fraser, Bardzo dziękuję za odpowiedź. Niestety, w środowisku, w którym chcę napisać testy, w końcu nie mam ani lambd, ani doładowania (używam boost tylko do szybkiego napisania demo problemu). Niemniej jednak twoja odpowiedź zainspirowała mnie do prostego rozwiązania przy użyciu wyspecjalizowanej akcji GMock (rodzaj surogatu dla lambda). –

0

Odpowiedź Frasera również mnie zainspirowała. Użyłem jego sugestii i zadziałało, ale potem znalazłem inny sposób na osiągnięcie tego samego bez zmiennej warunku. Musisz dodać metodę, by sprawdzić jakiś stan, a będziesz potrzebować nieskończonej pętli. Zakłada to również, że masz osobny wątek, który zaktualizuje warunek.

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(); 
} 
Powiązane problemy