Есть ли в std :: chrono средства, помогающие вводить system_clock для модульного тестирования

Я зависим от оборудования, которое может или не может ответить. Как следствие, я часто пишу функции с таймаутами. Системное время является известным источником для хрупких модульных тестов, поэтому введение контролируемого и стабильного времени кажется хорошей идеей для тестирования.

Интересно, есть ли какие-нибудь средства в 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?

  • «Что именно вы тестируете?» Я контролирую время как условие тестирования, чтобы изменить поведение вызовов методов, которые зависят от времени. Тест показывает, что подделка времени и управление поведением как концепция работает, и показывает мое понимание этого. Смысл минимального примера состоит в том, чтобы показать мое понимание времени насмешки, чтобы было легче показать различия с std:: объекты.
  • «Потратьте ~ 10 слов, сказав, что тесты должны противопоставить.» Один тест всегда истекает. Другое испытание не показывает времени. Третий тест, который контролирует точное и ненулевое течение времени, не был включен.
  • «Кроме того, сон не имеет ничего общего с часами. Это не хронографическая функция». Мне нужно было убедиться, что один тест никогда не зацикливается больше, чем на определенную величину до истечения времени ожидания, это имитирует некоторые действия, которые требуют времени и могут истечь , С другой стороны, я хотел встроить ярлык, чтобы второй тест не тратил время на ожидание. Было бы неплохо не издеваться и над Сном, но тест занял бы 2 секунды. Я понимаю, что сон не является хронологической функцией и поэтому вводит в заблуждение.

7

Решение

Похоже, что вы издеваетесь std::this_thread::sleep,

Это немного сложнее, потому что это пространство имен только с бесплатными функциями. Трудно «внедрить» пространство имен в целях тестирования. Таким образом, вы действительно должны обернуть функции из этого пространства имен своим собственным типом.

Я бы использовал внедрение статических зависимостей, а-ля C ++:

Жить на Колиру

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

Другие решения

Другой способ справиться с этим — определить смоделированные часы и указать тип часов, которые будут использоваться в качестве параметра шаблона.

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

#include "sim_clock.hpp"
using namespace std::chrono;

template <typename clock_t> void Sleep(milliseconds ms)
{
std::this_thread::sleep_for(ms);
}

template <> void Sleep<sim_clock>(milliseconds ms)
{
sim_clock::increment_by(ms);
}

template <typename clock_t = std::chrono::steady_clock> class CanTimeOut
{
public:
CanTimeOut() = default;
~CanTimeOut() = default;

milliseconds TimeoutAction(milliseconds maxtime)
{
int x = 0;
auto start = clock_t::now();
auto timeout = start + maxtime;
while(timeout > clock_t::now()) { Sleep<clock_t>(milliseconds(1)); }
return duration_cast<milliseconds>(clock_t::now() - start);
}
};

#define EXPECT_GE(left, right, test)                                           \
{                                                                          \
if(!(left >= right)) {                                                 \
std::cout << #test << " "                                          \
<< "!(" << left << " >= " << right << ")" << 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<sim_clock> cto;
long long timeout = 1000;
milliseconds actual = cto.TimeoutAction(milliseconds(timeout));
sim_clock::increment_by(milliseconds(timeout));
EXPECT_GE(actual.count(), timeout, TestWithSystemClock);
}

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

Вот смоделированное определение часов, основанное на steady_clock:

#pragma once
#include <chrono>

struct sim_clock {
typedef std::chrono::steady_clock::rep rep;
typedef std::chrono::steady_clock::period period;
typedef std::chrono::steady_clock::duration duration;
typedef std::chrono::steady_clock::time_point time_point;

static time_point now() noexcept;
static void increment_by(sim_clock::duration d) noexcept;

static constexpr bool is_steady = true;

static time_point _now;
};

и реализация:

#include "sim_clock.hpp"
sim_clock::time_point sim_clock::_now;

sim_clock::time_point sim_clock::now() noexcept
{
return _now;
}

void sim_clock::increment_by(sim_clock::duration d) noexcept
{
_now += d;
}
1

По вопросам рекламы [email protected]