我有一个非常基本的方法,可以对条件变量(从C ++中的wait()
调用<condition_variable>
。
void WaitForEvent() {
std::unique_lock<std::mutex> lock(mutex_);
while (!event_counter_) {
cond_var_.wait(lock);
}
return;
}
现在,我要对该方法的交互进行单元测试。因此,我想调用WaitForEvent
,然后调用PostEvent
,它将通知条件变量并检查等待是否已停止。
void PostEvent() {
// ...
cond_var_.notify_all();
}
我最好如何在单元测试中做到这一点?
到目前为止,我唯一想出的解决方案是
WaitForEvent
WaitForEvent
调用之后使线程设置标志PostEvent
但是,我不喜欢无法在此处加入线程这一事实。如果我加入,那么如果WaitForEvent
没有按预期解除封锁,我的测试将会封锁。另外,我不喜欢在单元测试中增加延迟。
我的问题有更好的解决方案吗?在此先感谢:)
编辑1:我的第一个解决方案如下
SomeClass some_class{};
bool has_unblocked = false;
std::thread blocking_thread([&] {
some_class.WaitForEvent();
has_unblocked = true;
});
some_class.PostEvent();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
REQUIRE(has_unblocked == true);
blocking_thread.join();
答案 0 :(得分:2)
我在一些涉及线程和线程间的mashalling事件/消息的单元测试中做了类似的事情。您提出的建议并不坏。
关于您的问题的一件突出事是,您的单元测试似乎只是测试std::condition_variable
的行为,而不是使用cond_var实现的代码的行为。我总是告诉我的团队不要编写只测试平台和c ++运行时的测试,因为我们知道这是可行的。我怀疑除了“等待和发布”代码外,您的测试还有更多内容,但这只是我想指出的内容。因为一旦意识到这一点,您就可以考虑在单元测试中仅覆盖Wait and Post代码。
如果我加入,那么如果WaitForEvent没有按预期解除阻止,则我的测试将阻止。
但是在成功的情况下,这并不重要,因为您的代码可以正常工作并且测试可以快速完成。如果确实永久阻止,则您的单元测试已经发现了一个真正的错误。那是一件好事吧? (除了您的团队成员抱怨单元测试再次挂起。)
但是,如果要避免在UT代码中出现死锁的可能性,可以使用wait_for
而不是wait
来改变等待时间。产品代码可能不依赖于超时,而UT却依赖于超时。...
class SomeClass
{
bool _running_as_unittest;
void WaitForEvent() {
std::unique_lock<std::mutex> lock(mutex_);
while (!event_counter_) {
if (_running_as_unittest) {
cond_var.wait(lock, FiveSeconds); // give the test a chance to escape
if (!event_counter) {
_errorCondition = true;
}
}
else {
cond_var.wait(lock); // wait forever
}
}
return;
}
…
}
然后输入测试代码:
SomeClass some_class{};
bool has_unblocked = false;
some_class._running_as_unittest = true;
some_class._errorCondition = false;
std::thread blocking_thread([&] {
some_class.WaitForEvent();
has_unblocked = true;
});
some_class.PostEvent();
for (int x = 0; (x < 500) && !has_unblocked; x++) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
REQUIRE(has_unblocked == true);
REQUIRE(someclass._errorCondition == false);
// todo - if has_unblocked is false, you could consider invoking `blocking_thread.native_handle` and then invoking `pthread_kill` or `TerminateThread` as appropriate. instead of invoking `join`.
blocking_thread.join();
答案 1 :(得分:1)
在这种情况下,我通常使用std::async
返回一个std::future
,并指定操作超时以避免测试卡在CI中。
#include <future>
#include <condition_variable>
#include <chrono>
#include <mutex>
class MySystem {
public:
MySystem() = default;
std::future<bool> WaitForEvent(std::chrono::milliseconds timeout) {
std::unique_lock<std::mutex> l(cv_m_);
return std::async(std::launch::async,
// for this lambda you need C++14
[this, timeout, lock{std::move(l)}] () mutable {
if (cv_.wait_for(lock, timeout) == std::cv_status::timeout) {
return false;
}
return true;
});
}
void PostEvent() {
std::lock_guard<std::mutex> guard(cv_m_);
cv_.notify_all();
}
private:
std::condition_variable cv_;
std::mutex cv_m_;
};
TEST(xxx, yyy) {
MySystem sut;
auto result = sut.waitForEvent(std::chrono::seconds(1));
ASSERT_FALSE(result.get());
result = sut.waitForEvent(std::chrono::seconds(1));
sut.PostEvent();
ASSERT_TRUE(result.get());
}
std::future
节省了我手动创建和连接线程的工作,并让我可以很好地检查操作结果(例如,false
超时)。在调用std::mutex
之前锁定std::async
可以确保您的condition_variable
将在执行notify_all
之前开始等待。