2012-05-26 3 views
9

Каким будет лучший способ записи (google) тестовых примеров с использованием объекта mock google и ожидать, что определения EXPECT_CALL() будут вызваны из другого потока, контролируемого классом в тесте? Просто вызов sleep() или аналогичный после запуска последовательности вызовов не устраивает, так как он может замедлить тестирование ненужным и может не повлиять на условия синхронизации. Но завершение тестового случая каким-то образом должно подождать, пока не будут вызваны ложные методы. Идеи кто-нибудь?Ожидание вызовов googlemock из другого потока

Вот код, чтобы проиллюстрировать ситуацию:

Bar.hpp (класс тестируемой)

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 (выдержка):

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

Код проверки (выдержка, ничего особенного в определении FooInterfaceMock пока):

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

Результаты испытаний без сна():

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

Результаты тестов со сном() включены:

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

Я хочу, чтобы избежать сна(), в лучшем случае, без необходимости изменения Бара класса вообще.

ответ

12

Ответ Фрейзера вдохновил меня на простое решение с использованием специализированного действия GMock. GMock очень легко быстро пишет такие действия.

Вот код (выдержка из 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(); 
} 

Примечание тот же принцип будет хорошо работать для любого другого вида семафоров реализации как boost::interprocess::interprocess_semaphore. Я использую его для тестирования с помощью нашего производственного кода, который использует собственный уровень абстракции ОС и реализацию семафора.

+0

'timed_wait()' не будет работать должным образом, если вы используете 'local_time()' при расчете 'до'. Вместо этого вы должны использовать 'universal_time()'. – Rom098

+0

@ Rom098 THX для подсказки.Реальный пример, который я использую в своем собственном OSAL, я поставил здесь функции boost как сжатый суррогат. –

5

Использования лямбда, вы могли бы сделать что-то подобное (я поставил подталкивание эквивалентов в комментариях):

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

Если вы не можете использовать лямбды, я полагаю, вы могли бы использовать вместо boost::bind.

+0

Привет, Фрейзер, Большое спасибо за ваш ответ. К сожалению, в среде, где я хочу написать тесты, в конце концов, у меня нет ни лямбда, ни повышения (я использовал boost только для быстрого создания демонстрации проблемы). Тем не менее, ваш ответ вдохновил меня на простое решение, используя специализированное действие GMock (вид суррогата для лямбда). –

0

Ответ Фрейзера вдохновил меня также. Я использовал его предложение, и это сработало, но затем я нашел другой способ выполнить то же самое без переменной условия. Вам нужно будет добавить метод для проверки некоторого состояния, и вам понадобится бесконечный цикл. Это также предполагает, что у вас есть отдельный поток, который обновит условие.

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(); 
} 
Смежные вопросы