如何在C ++中测试条件变量?

时间:2019-04-11 07:17:30

标签: c++ multithreading testing

我有一个非常基本的方法,可以对条件变量(从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();
}

我最好如何在单元测试中做到这一点?

到目前为止,我唯一想出的解决方案是

  1. 启动一个调用WaitForEvent
  2. 的线程
  3. WaitForEvent调用之后使线程设置标志
  4. 在主线程中调用PostEvent
  5. 在主线程中等待x秒
  6. 检查标志是否已设置

但是,我不喜欢无法在此处加入线程这一事实。如果我加入,那么如果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();

2 个答案:

答案 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之前开始等待。