Ожидание вызовов GoogleMock из другого потока
Каков будет наилучший способ написания (google) тестовых случаев с использованием фиктивного объекта google и ожидания вызовов EXPECT_CALL() из другого потока, контролируемого тестируемым классом? Простой вызов sleep () или тому подобное после запуска последовательностей вызовов не кажется подходящим, поскольку это может замедлить ненужное тестирование и может не повлиять на условия синхронизации. Но завершение теста как-то должно ждать, пока не будут вызваны фиктивные методы. Идеи кого-нибудь?
Вот некоторый код, чтобы проиллюстрировать ситуацию:
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
Результаты теста с включенным sleep ():
[==========] 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.
Я хочу избежать сна (), в лучшем случае без необходимости вообще менять класс Bar.
3 ответа
Ответ Фрейзера вдохновил меня на простое решение с использованием специализированного действия 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
, Я использую его для тестирования нашего производственного кода, который использует собственный уровень абстракции ОС и реализацию семафора.
Используя лямбда-выражения, вы можете сделать что-то вроде (я поместил буст-эквиваленты в комментарии):
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
вместо.
Мне понравились эти решения, но я подумал, что с обещанием будет проще, мне пришлось дождаться запуска моего теста:
std::promise<void> started;
EXPECT_CALL(mock, start_test())
.Times(1)
.WillOnce(testing::Invoke([&started]() {
started.set_value();
}));
system_->start();
EXPECT_EQ(std::future_status::ready, started.get_future().wait_for(std::chrono::seconds(3)));
Мне удалось решить проблему после того, как было предложено решение πάντα ῥε st, но с помощью std::condition_variable. Решение стало немного отличаться от предложенного Фрейзером и также может быть улучшено с помощью лямбда-выражений.
ACTION_P(ReturnFromAsyncCall, cv)
{
cv->notify_all();
}
...
TEST_F(..,..)
{
std::condition_variable cv;
...
EXPECT_CALL(...).WillRepeatedly(ReturnFromAsyncCall(&cv));
std::mutex mx;
std::unique_lock<std::mutex> lock(mx);
cv.wait_for(lock, std::chrono::seconds(1));
}
Кажется, здесь мьютекс только для того, чтобы удовлетворить условную переменную.
Ответ Фрейзера меня тоже вдохновил. Я использовал его предложение, и оно сработало, но затем я нашел другой способ сделать то же самое без условной переменной. Вам нужно добавить метод для проверки некоторых условий, и вам понадобится бесконечный цикл. Это также предполагает, что у вас есть отдельный поток, который будет обновлять условие.
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();
}