【问题标题】:Expecting googlemock calls from another thread期待来自另一个线程的 googlemock 调用
【发布时间】:2012-05-26 14:36:30
【问题描述】:

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

没有 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

启用 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.

我想避免 sleep(),最好的情况是根本不需要更改 Bar 类。

【问题讨论】:

    标签: c++ mocking googlemock


    【解决方案1】:

    Fraser 的回答启发了我使用 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 一样,同样的原则也适用于任何其他类型的信号量实现。我用它来测试我们的生产代码,这些代码使用它自己的操作系统抽象层和信号量实现。

    【讨论】:

    • 如果在计算until时使用local_time()timed_wait()将无法正常工作。你应该改用universal_time()
    • @Rom098 THX 提示。我使用我们自己的 OSAL 的真实示例,我把 boost 函数作为一个简洁的代理。
    【解决方案2】:

    使用 lambda,您可以执行类似的操作(我在 cmets 中放入了 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();
    }
    

    如果您不能使用 lambda,我想您可以改用 boost::bind

    【讨论】:

    • 您好弗雷泽,非常感谢您的回答。不幸的是,在我最终想编写测试的环境中,我既没有 lambda,也没有可用的 boost(我一直在使用 boost 来快速编写问题的演示)。不过,您的回答启发了我使用专门的 GMock 动作(一种 lambda 的代理)的简单解决方案。
    【解决方案3】:

    所以我喜欢这些解决方案,但认为通过承诺可能会更容易,我不得不等待我的测试启动:

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

    【讨论】:

      【解决方案4】:

      弗雷泽的回答也启发了我。我使用了他的建议,它奏效了,但后来我找到了另一种没有条件变量的方法来完成同样的任务。您需要添加一个方法来检查某些条件,并且您需要一个无限循环。这也是假设您有一个单独的线程来更新条件。

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

      【讨论】:

      • 我不认为通过主动等待(即无限循环)替换条件变量是一个很好的权衡。我通常认为恰恰相反:通过使用条件变量来消除主动等待是个好主意。您对此有何看法?
      【解决方案5】:

      在 πάντα ῥεῖ 解决方案提出后,我已经设法解决了这个问题,但使用了 std::condition_variable。该解决方案与 Fraser 提出的方案有些不同,也可以使用 lambdas 进行改进。

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

      这里似乎互斥锁只是为了满足条件变量。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-21
        • 2017-02-07
        • 1970-01-01
        相关资源
        最近更新 更多