10

応答する場合と応答しない場合があるハードウェアに依存しています。結果として、私はしばしばタイムアウト付きの関数を書くことになります。システム時間は脆弱な単体テストの既知のソースであるため、制御された安定した時間をテストに挿入することは良い考えのようです。

std::chrono にそれを助ける機能があるのだろうか。私が見る別の方法は、システム時間のラッパーを作成し、そのアダプターに依存することです。

以下は、ラッパーがどのように見えるかの最小限の例です。

#pragma once
#include <memory>
#include <chrono>
#include <thread>
#include <iostream>

using std::chrono::system_clock;
using std::chrono::milliseconds;
using std::shared_ptr;
using std::make_shared;

class Wrapped_Clock
{
public:
    virtual system_clock::time_point Now() { return system_clock::now(); }
    virtual void Sleep(milliseconds ms) { std::this_thread::sleep_for(ms); }
};

class Mock_Clock : public Wrapped_Clock
{
private:
    system_clock::time_point now;
public:
    Mock_Clock() : now(system_clock::now()){}
    ~Mock_Clock() {}
    system_clock::time_point Now() { return now; }
    void Sleep(milliseconds ms) { }
};

class CanTimeOut
{
private:
    shared_ptr<Wrapped_Clock> sclock;
public:
    CanTimeOut(shared_ptr<Wrapped_Clock> sclock = make_shared<Wrapped_Clock>()) : sclock(sclock) {}
    ~CanTimeOut() {}

    milliseconds TimeoutAction(milliseconds maxtime)
    {
        using std::chrono::duration_cast;
        int x = 0;
        system_clock::time_point start = sclock->Now();
        system_clock::time_point timeout = sclock->Now() + maxtime;
        while (timeout > sclock->Now() && x != 2000)
        {
            sclock->Sleep(milliseconds(1));
            ++x;
        }
        milliseconds elapsed = duration_cast<milliseconds>(sclock->Now() - start);
        return elapsed;
    }

};

#define EXPECT_GE(left, right, test) \
{ if (!(left >= right)) { \
    std::cout << #test << " " << "!(" << left << " >= " << right << ")" << std::endl; \
} }

#define EXPECT_EQ(expected, actual, test) \
{ if (!(expected == actual)) { \
    std::cout << #test << " " << "!(" << expected << " == " << actual << ")" << std::endl; \
} }

void TestWithSystemClock()
{
    CanTimeOut cto;
    long long timeout = 1000;
    milliseconds actual = cto.TimeoutAction(milliseconds(timeout));
    EXPECT_GE(actual.count(), timeout, TestWithSystemClock);
}

void TestWithMockClock()
{
    CanTimeOut cto(make_shared<Mock_Clock>());
    milliseconds actual = cto.TimeoutAction(milliseconds(1000));
    EXPECT_EQ(0, actual.count(), TestWithMockClock);
}

int main()
{
    TestWithSystemClock();
    TestWithMockClock();
}

これのうち、std::chrone の機能でどの程度置き換えることができますか?

編集1:

  • 「具体的に何をテストしているのですか?」時間に依存するメソッド呼び出しの振る舞いを変えるためのテスト条件として時間を制御しています。このテストは、時間を嘲笑し、行動を概念として制御することが機能することを示しており、それに対する私の理解を示しています。std::最小限の例のポイントは、施設の違いを示しやすくするために、時間をモックすることについての私の理解を示すことです。
  • 「テストが何を対比すべきかについて、10 語以内で説明してください。」1 つのテストは常にタイムアウトします。もう 1 つのテストでは、時間の経過は示されません。正確で非ゼロの時間経過を制御する 3 番目のテストは含まれていません。
  • 「それに、睡眠は時計とは関係ありません。クロノ機能ではありません。」1 つのテストがタイムアウトする前に特定の量を超えてループしないようにするために必要でした。これは、時間がかかり、タイムアウトする可能性のあるアクションをシミュレートします。一方で、2 番目のテストで待ち時間が無駄にならないように、ショートカットを組み込みたいと考えました。Sleep もモックしなくても問題ありませんが、テストには 2 秒かかります。スリープはクロノ機能ではないため、誤解を招くという点は認識しています。
4

2 に答える 2

8

代わりに、あなたが嘲笑しているように見えますstd::this_thread::sleep

これは、フリー関数だけの名前空間であるため、少しトリッキーです。テスト目的で名前空間を「注入」するのは困難です。したがって、実際には、その名前空間の関数を独自の型でラップする必要があります。

C++のような静的依存性注入を使用します。

Live On Coliru

#include <memory>
#include <chrono>
#include <thread>
#include <iostream>

using std::chrono::system_clock;
using std::chrono::milliseconds;

struct production {
    using clock = std::chrono::system_clock;

    struct this_thread {
        template<typename... A> static auto sleep_for(A&&... a) { return std::this_thread::sleep_for(std::forward<A>(a)...); }
        template<typename... A> static auto sleep_until(A&&... a) { return std::this_thread::sleep_until(std::forward<A>(a)...); }
    };
};

struct mock {
    struct clock : std::chrono::system_clock {
        using base_type = std::chrono::system_clock;
        static time_point now() { static auto onetime = base_type::now(); return onetime; }
    };

    struct this_thread {
        template<typename... A> static auto sleep_for(A&&... a) {}
        template<typename... A> static auto sleep_until(A&&... a) {}
    };
};

template <typename services = production,
         typename clock = typename services::clock,
         typename this_thread = typename services::this_thread>
class CanTimeOut
{
public:
    milliseconds TimeoutAction(milliseconds maxtime)
    {
        using std::chrono::duration_cast;

        int x = 0;
        auto start   = clock::now();
        auto timeout = clock::now() + maxtime;
        while (timeout > clock::now() && x != 2000)
        {
            this_thread::sleep_for(milliseconds(1));
            ++x;
        }
        milliseconds elapsed = duration_cast<milliseconds>(clock::now() - start);
        return elapsed;
    }

};

#define EXPECT_GE(left, right, test) \
{ if (!(left >= right)) { \
    std::cout << #test << " " << "!(" << left << " >= " << right << ")" << std::endl; \
} }

#define EXPECT_EQ(expected, actual, test) \
{ if (!(expected == actual)) { \
    std::cout << #test << " " << "!(" << expected << " == " << actual << ")" << std::endl; \
} }

void TestWithSystemClock()
{
    CanTimeOut<> cto;
    long long timeout = 1000;
    milliseconds actual = cto.TimeoutAction(milliseconds(timeout));
    EXPECT_GE(actual.count(), timeout, TestWithSystemClock);
}

void TestWithMockClock()
{
    CanTimeOut<mock> cto;
    milliseconds actual = cto.TimeoutAction(milliseconds(1000));
    EXPECT_EQ(0, actual.count(), TestWithMockClock);
}

int main()
{
    TestWithSystemClock();
    TestWithMockClock();
}
于 2015-11-09T10:46:40.483 に答える