13

グーグルモックオブジェクトを使用して(グーグル)テストケースを記述し、テストのクラスによって制御される別のスレッドから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.

最良の場合、Barクラスをまったく変更せずにsleep()を避けたいと思います。

4

5 に答える 5

15

フレイザーの答えは、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。独自のOS抽象化レイヤーとセマフォ実装を使用する本番コードでのテストに使用しています。

于 2012-05-28T10:36:31.083 に答える
7

ラムダを使用すると、次のようなことができます(コメントに同等のブーストを入れました):

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代わりに使用できると思います。

于 2012-05-27T19:23:22.773 に答える
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)));
于 2019-07-09T11:26:55.230 に答える
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();
}
于 2016-10-27T14:30:51.577 に答える
0

πάνταῥεῖソリューションによって提案された後、私はなんとか問題を解決しましたが、std::condition_variableを使用しました。このソリューションは、Fraserが提案したものとは少し異なり、ラムダを使用して改善される可能性もあります。

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

条件変数を満たすためだけに、ここではミューテックスのようです。

于 2020-07-31T15:33:33.217 に答える