std::chrono 中是否有设施可以协助注入 system_clock 进行单元测试
Are there facilities in std::chrono to assist with injecting system_clock for unit testing
我依赖于可能响应也可能不响应的硬件。因此,我经常以编写带有超时的函数而告终。系统时间是脆弱单元测试的已知来源,因此注入受控且稳定的时间似乎是测试的好主意。
我想知道 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 个词来说明测试应该对比什么。”
一个测试总是超时。另一个测试显示没有时间流逝。不包括控制精确和非零时间流逝的第三个测试。
- “此外,睡眠与时钟无关。它不是计时功能。”
我需要它来确保一个测试在超时之前不会循环超过一定数量,这模拟了一些需要时间并且可能超时的动作。另一方面,我想建立一个快捷方式,这样第二次测试就不会浪费时间等待。也可以不模拟睡眠,但测试需要 2 秒。我认识到睡眠不是计时功能,因此具有误导性。
相反,您似乎在嘲笑 std::this_thread::sleep
。
这有点棘手,因为它是一个只有自由函数的命名空间。很难 "inject" 命名空间用于测试目的。所以,你确实应该用你自己的类型包装来自那个命名空间的函数。
我会使用静态依赖注入,就像 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();
}
处理此问题的另一种方法是定义模拟时钟并指定要用作模板参数的时钟类型。
#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;
}
我依赖于可能响应也可能不响应的硬件。因此,我经常以编写带有超时的函数而告终。系统时间是脆弱单元测试的已知来源,因此注入受控且稳定的时间似乎是测试的好主意。
我想知道 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 个词来说明测试应该对比什么。” 一个测试总是超时。另一个测试显示没有时间流逝。不包括控制精确和非零时间流逝的第三个测试。
- “此外,睡眠与时钟无关。它不是计时功能。” 我需要它来确保一个测试在超时之前不会循环超过一定数量,这模拟了一些需要时间并且可能超时的动作。另一方面,我想建立一个快捷方式,这样第二次测试就不会浪费时间等待。也可以不模拟睡眠,但测试需要 2 秒。我认识到睡眠不是计时功能,因此具有误导性。
相反,您似乎在嘲笑 std::this_thread::sleep
。
这有点棘手,因为它是一个只有自由函数的命名空间。很难 "inject" 命名空间用于测试目的。所以,你确实应该用你自己的类型包装来自那个命名空间的函数。
我会使用静态依赖注入,就像 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();
}
处理此问题的另一种方法是定义模拟时钟并指定要用作模板参数的时钟类型。
#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;
}