简体   繁体   English

期待来自另一个线程的 googlemock 调用

[英]Expecting googlemock calls from another thread

What will be the best way to write (google) test cases using a google mock object and expect the EXPECT_CALL() definitions being called from another thread controlled by the class in test?使用谷歌模拟对象编写(谷歌)测试用例并期望从测试中的类控制的另一个线程调用 EXPECT_CALL() 定义的最佳方法是什么? Simply calling sleep() or alike after triggering the call sequences doesn't feel appropriate since it may slow down testing unnecessary and may not really hit the timing conditions.在触发调用序列后简单地调用 sleep() 或类似方法并不合适,因为它可能会减慢不必要的测试,并且可能不会真正达到计时条件。 But finishing the test case somehow has to wait until the mock methods have been called.但是以某种方式完成测试用例必须等到模拟方法被调用。 Ideas anyone?任何人的想法?

Here's some code to illustrate the situation:下面是一些代码来说明这种情况:

Bar.hpp (the class under test) 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 (excerpt): 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);
    }
}

The testing code (excerpt, nothing special in the definition of FooInterfaceMock so far):测试代码(摘录,到目前为止 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();
}

Test results without sleep():没有 sleep() 的测试结果:

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

Test results with sleep() enabled:启用 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.

I want to avoid the sleep(), in best case without need to change the Bar class at all.我想避免使用 sleep(),最好的情况是根本不需要更改 Bar 类。

Fraser's answer inspired me for a simple solution using a GMock specialized Action. Fraser 的回答激发了我使用 GMock 专用 Action 的简单解决方案。 GMock makes it very easy to quickly write such Actions. GMock 使快速编写此类操作变得非常容易。

Here's the code (excerpt from BarTest.cpp):这是代码(摘自 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();
}

Note the same principle will work well for any other kind of semaphore implementation as boost::interprocess::interprocess_semaphore .请注意,相同的原则适用于任何其他类型的信号量实现,如boost::interprocess::interprocess_semaphore I'm using it for testing with our production code that uses it's own OS abstraction layer and semaphore implementation.我用它来测试我们的生产代码,这些代码使用它自己的操作系统抽象层和信号量实现。

Using lambdas, you could do something like (I have put boost equivalents in comments):使用 lambdas,你可以做一些类似的事情(我在评论中加入了 boost 等价物):

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

If you can't use lambdas, I imagine you could useboost::bind instead.如果您不能使用 lambda,我想您可以改用boost::bind

So I liked these solutions, but thought it might be easier with a promise, I had to wait for my test to startup:所以我喜欢这些解决方案,但认为有一个承诺可能会更容易,我不得不等待我的测试启动:

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

Fraser's answer inspired me also.弗雷泽的回答也启发了我。 I used his suggestion, and it worked, but then I found another way to accomplish the same without the condition variable.我使用了他的建议,它奏效了,但后来我找到了另一种没有条件变量的方法来完成同样的工作。 You'll need to add a method to check some condition, and you'll need an infinite loop.您需要添加一个方法来检查某些条件,并且需要一个无限循环。 This is also assuming that you have a separate thread that will update the condition.这也假设您有一个单独的线程来更新条件。

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

I've managed to solve the issue after proposed by πάντα ῥεῖ solution, but with std::condition_variable.在 πάντα ῥεῖ 解决方案提出后,我设法解决了这个问题,但使用 std::condition_variable。 The solution become a bit different than Fraser proposed and also may be improved with lambdas.该解决方案与 Fraser 提出的方案略有不同,也可以使用 lambda 进行改进。

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

Seems mutex here just to sutisfy condition variable.这里似乎互斥锁只是为了满足条件变量。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM