异步函数测试不能直接用expect_eq,因为异步操作结果不会立即返回,断言执行前测试已结束;应使用std::condition_variable同步、std::future::wait_for带超时等待,或gmock验证回调行为。

异步函数测试为什么不能直接用 EXPECT_EQ
因为异步调用(比如带回调、future、std::async 或 event loop 中的 deferred)的执行结果不会立刻返回,EXPECT_EQ 一跑就过,根本等不到回调触发或 future 就绪——测试会提前结束,断言根本没机会执行。
常见错误现象:[ OK ] 显示测试通过,但实际回调压根没进;或者测试进程直接退出,日志里连 callback 打印都没有。
- 别在测试里裸写
std::this_thread::sleep_for等几秒——不可靠、拖慢 CI、且无法覆盖超时路径 - 别依赖全局状态或静态 flag 判断回调是否执行——多测试并发跑时容易污染
- gtest 本身不提供 await 或 timeout 语义,必须自己建同步机制
TEST_F + std::condition_variable 是最可控的同步方式
需要一个 fixture 来封装等待逻辑,让每个测试独享条件变量和互斥锁,避免跨测试干扰。
示例:测一个接受 std::function<void></void> 回调的异步加法
#include <gtest>
#include <thread>
#include <mutex>
#include <condition_variable>
class AsyncTest : public ::testing::Test {
protected:
std::mutex mtx_;
std::condition_variable cv_;
bool done_ = false;
int result_ = 0;
void WaitUntilDone() {
std::unique_lock<:mutex> lock(mtx_);
cv_.wait(lock, [this] { return done_; });
}
void NotifyDone(int val) {
std::unique_lock<:mutex> lock(mtx_);
result_ = val;
done_ = true;
cv_.notify_one();
}
};
TEST_F(AsyncTest, AddAsyncReturnsCorrectResult) {
auto async_add = [](int a, int b, std::function<void> cb) {
std::thread([=]() { cb(a + b); }).detach();
};
async_add(2, 3, [this](int res) { NotifyDone(res); });
WaitUntilDone();
EXPECT_EQ(result_, 5);
}</void></:mutex></:mutex></condition_variable></mutex></thread></gtest>
- 每个
TEST_F实例自带独立done_和result_,不用 static -
WaitUntilDone()用cv_.wait()阻塞直到回调触发,不忙等 - 务必 detach 或 join 线程,否则测试进程可能 crash(尤其 Windows 上)
用 std::future 的测试更简洁,但要注意 get() 超时风险
如果被测函数返回 std::future(比如 std::async 包装),优先用 wait_for 带超时,避免死等。
在 Go 中使用 google/wire 实现编译时依赖注入——wire.NewSet、wire.Build、wire.Bind(接口→实现)、wire.Struct、wire.Value、wire.Interface
错误写法:future.get() —— 一旦 future 永不就绪,测试卡死
正确写法:
TEST(AsyncFutureTest, FutureCompletesInTime) {
auto fut = std::async(std::launch::async, []() {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
return 42;
});
auto status = fut.wait_for(std::chrono::milliseconds(100));
EXPECT_EQ(status, std::future_status::ready);
EXPECT_EQ(fut.get(), 42);
}
-
wait_for返回std::future_status::ready/::timeout/::deferred,必须检查 - 只在确认
ready后才调get(),否则抛std::future_error - 超时时间设为实际耗时的 2–3 倍,太短易误报,太长拖慢整体
Mock 回调 + EXPECT_CALL 适合验证行为而非值
当重点是“回调是否被调用”“参数是否正确”“调用顺序是否符合预期”,而不是等具体返回值,用 gmock 更自然。
前提是你的异步接口支持注入 mock 回调(比如把 std::function 改成模板参数或接口指针):
#include <gmock>
class CallbackInterface {
public:
virtual ~CallbackInterface() = default;
virtual void OnResult(int value) = 0;
};
TEST(AsyncMockTest, CallbackIsInvokedWithExpectedValue) {
testing::StrictMock<mockcallback> mock_cb;
EXPECT_CALL(mock_cb, OnResult(7)).Times(1);
auto async_op = [](std::unique_ptr<callbackinterface> cb) {
std::thread([cb = std::move(cb)]() {
cb->OnResult(7);
}).detach();
};
async_op(std::make_unique<mockcallback>(mock_cb));
std::this_thread::sleep_for(std::chrono::milliseconds(10)); // 粗略等线程启动
}</mockcallback></callbackinterface></mockcallback></gmock>
-
StrictMock确保没多余调用;EXPECT_CALL明确声明期望行为 - 仍需 sleep 或 condition_variable 等待线程调度——gmock 不解决同步问题
- 不要在 mock 里做 heavy work,否则影响测试稳定性
condition_variable,也要考虑线程调度延迟、系统负载导致的 wait 偏差;mock 方式看似干净,却可能掩盖回调未按预期时机触发的问题。选哪种方式,取决于你真正想验证的是“结果对不对”,还是“流程走没走对”。C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










