C++单元测试常见问题总结

一、异步接口测试常见方法

1.runloop阻塞调用线程等待异步执行结果。例如chromium的单测,

//单测
TEST_F(HttpNetworkTransactionTest, Incomplete100ThenEOF) {
  HttpRequestInfo request;
  request.method = "POST";
  request.url = GURL("http://www.foo.com/");
  request.traffic_annotation =
      net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);

  std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
  HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());

  MockRead data_reads[] = {
    MockRead(SYNCHRONOUS, "HTTP/1.0 100 Continue\r\n"),
    MockRead(ASYNC, 0),
  };
  StaticSocketDataProvider data(data_reads, base::span<MockWrite>());
  session_deps_.socket_factory->AddSocketDataProvider(&data);

  TestCompletionCallback callback;

  int rv = trans.Start(&request, callback.callback(), NetLogWithSource());
  EXPECT_THAT(rv, IsError(ERR_IO_PENDING));

  rv = callback.WaitForResult();
  EXPECT_THAT(rv, IsOk());

  std::string response_data;
  rv = ReadTransaction(&trans, &response_data);
  EXPECT_THAT(rv, IsOk());
  EXPECT_EQ("", response_data);
}

//WaitForResult实现

R WaitForResult() {
  TestCompletionCallbackBaseInternal::WaitForResult();
  return std::move(result_);
}

void TestCompletionCallbackBaseInternal::WaitForResult() {
  DCHECK(!run_loop_);
  if (!have_result_) {
    run_loop_ = std::make_unique<base::RunLoop>(
        base::RunLoop::Type::kNestableTasksAllowed);
    run_loop_->Run();
    run_loop_.reset();
    DCHECK(have_result_);
  }
  have_result_ = false;  // Auto-reset for next callback.
}

void RunLoop::Run(const Location& location) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // "test" tracing category is used here because in regular scenarios RunLoop
  // trace events are not useful (each process normally has one RunLoop covering
  // its entire lifetime) and might be confusing (they make idle processes look
  // non-idle). In tests, however, creating a RunLoop is a frequent and an
  // explicit action making this trace event very useful.
  TRACE_EVENT("test", "RunLoop::Run", "location", location);

  if (!BeforeRun())
    return;

  // If there is a RunLoopTimeout active then set the timeout.
  // TODO(crbug.com/905412): Use real-time for Run() timeouts so that they
  // can be applied even in tests which mock TimeTicks::Now().
  CancelableOnceClosure cancelable_timeout;
  const RunLoopTimeout* run_timeout = GetTimeoutForCurrentThread();
  if (run_timeout) {
    cancelable_timeout.Reset(BindOnce(&OnRunLoopTimeout, Unretained(this),
                                      location, run_timeout->on_timeout));
    origin_task_runner_->PostDelayedTask(
        FROM_HERE, cancelable_timeout.callback(), run_timeout->timeout);
  }

  DCHECK_EQ(this, delegate_->active_run_loops_.top());
  const bool application_tasks_allowed =
      delegate_->active_run_loops_.size() == 1U ||
      type_ == Type::kNestableTasksAllowed;
  delegate_->Run(application_tasks_allowed, TimeDelta::Max());

  AfterRun();
}

/*
大概思路是,声明TestCompletionCallback,并注册callback到测试接口中,然后TestCompletionCallback调用WaitForResult阻塞线程,当事件完成后,调用callback修改标记位,结束runloop阻塞
缺点:需要给项目开发异步测试工具类,如TestCompletionCallbackBase等;同时对代码具有一定的侵入性,如start接口需要提供事件完成的回调。
关键点:runloop结束的条件,一种是设置固定等待时间,然后结束loop判断异步调用结果;一种是设置标记位,loop检查标记位,当事件完成后设置标记位,停止loop。
*/

又如,iOS开发中XCode6以前的单元测试,发起异步调用后通过循环查询标志位来检查异步调用结果。

2.通过条件变量阻塞调用线程,当异步调用执行结束后通知条件变量,检测执行结果。如我自己的实现,

class ConfigServerUnitTest : public testing::Test {
public:
    static void SetUpTestCase() {}
    static void TearDownTestCase() {}

    virtual void SetUp(void) override {}
    virtual void TearDown(void) override {}

    //异步测试事件
    void wait_event(std::atomic_bool& event);
    void notify_event(std::atomic_bool& event);

public:
    static std::mutex _s_mutex;
    static std::condition_variable _s_condition_variable;
};

std::mutex ConfigServerUnitTest::_s_mutex;
std::condition_variable ConfigServerUnitTest::_s_condition_variable;

void ConfigServerUnitTest::wait_event(std::atomic_bool& event) {
    std::unique_lock<std::mutex> lock(_s_mutex);
    while (event == false) {
        _s_condition_variable.wait(lock);
    }
}

void ConfigServerUnitTest::notify_event(std::atomic_bool& event) {
    std::unique_lock<std::mutex> lock(_s_mutex);
    event = true;
    _s_condition_variable.notify_all();
}


//单元测试
TEST_F(ConfigServerUnitTest, connect_http) {
    std::atomic_bool connect_finished = false;
    bool connect_result = true;

    std::string valid_url = "";

    auto http_transmit = std::make_shared<HttpTransmit>(valid_url, true);
    http_transmit->set_callback([&](int ec) {
        ec ? connect_result = false : connect_result = true;
        
        //事件完成,通知
        notify_event(connect_finished);
    });

    http_transmit->connect(false);

    //等待异步调用完成
    wait_event(connect_finished);

    http_transmit->release();

    EXPECT_EQ(true, connect_result);
}

3.iOS提供的XCTestExpectation,尚未仔细研究,大概也是阻塞调用线程等待异步调用结果。

参考:

chromium单测,https://source.chromium.org/chromium/chromium/src/+/master:net/http/http_network_transaction_unittest.cc
​​​​​

二、私有方法是否需要单测,如何单元测试?

1.宏控制,私有方法的属性public和private的属性

2.friend声明

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值