结论
仓库中多处并发、event bus、rate limit、WebSocket、adapter 与 lifecycle 测试依赖固定 time.Sleep 或无 deadline 的 polling loop 来等待异步行为。
典型示例:
hub-server/internal/service/eventbus_test.go
- 多个测试在 Publish 后固定
Sleep(50ms);
for Running/Pending > 0 { Sleep(5ms) } 没有总 deadline。
- 搜索还命中:
- Hub middleware/ws/cache/bus/service tests;
- Edge events/adapters/lifecycle/hub tests;
- integration WS reconnect tests。
固定 sleep 既可能太短导致 CI 偶发失败,也可能过长拖慢测试;无 deadline polling 在缺陷出现时可能永久挂住。它们还掩盖了生产组件缺少 Wait/Drain/Clock seam 的设计问题。
风险
- 不同 CI 负载/平台下 flaky;
- race 模式更慢时概率性失败;
- 行为未发生时测试长时间挂死;
- 为降低 flake 不断增加 sleep,测试套件越来越慢;
- 无法精确证明 backoff、heartbeat、timeout、queue drain 的状态转换;
- 生产组件没有可观察 completion API,只能靠测试窥探内部计数器。
目标设计
建立仓库级测试等待原则:
事件完成
使用 channel、WaitGroup、barrier、callback probe 或组件提供的 WaitIdle(ctx):
select {
case got := <-handled:
// assert
case <-time.After(testDeadline):
t.Fatal("handler not called")
}
时间行为
- 注入 clock/ticker/timer factory;
- 使用 fake/manual clock 推进时间;
- heartbeat/backoff/TTL/rate-limit 不依赖真实秒级等待。
Polling
若必须 eventually:
- 使用统一 helper;
- 有明确 deadline、poll interval、失败时状态 dump;
- 禁止裸无限循环。
生产 API
对异步组件增加必要且非 test-only hack 的 lifecycle/observability:
WaitIdle(ctx);
Drain(ctx);
Start(ctx) / Close();
- injectable clock;
- completion/result channel。
实施步骤
- 建立脚本盘点
_test.go 中 time.Sleep、time.After、裸 polling;
- 分类:
- 合法的 deadline guard;
- 真实时间 integration;
- 可替换的同步等待;
- 需要 clock seam;
- 先处理高频/高 flake 包:Hub bus/ws/middleware,Edge events/lifecycle/adapters;
- 增加 ratchet:存量清单只能下降,新 unit test 不得新增固定 sleep;
- integration 中保留真实时间时必须有原因、上限和稳定性证据。
必须测试/验收
- 目标包
-count=50 稳定;
- race lane
-count=10;
- 测试总时长 before/after;
- 人工删除/阻断目标事件时,测试在短 deadline 内明确失败而非挂住;
- fake clock 能验证 1s/2s/4s backoff、heartbeat expiry、TTL;
- verifier 自测:新增
time.Sleep fixture 会失败,合法 allowlist 可解释。
推荐命令:
go test ./hub-server/internal/bus/... -count=50
go test -race ./hub-server/internal/bus/... -count=10
go test ./edge-server/internal/events/... ./edge-server/internal/lifecycle/... -count=50
按实际 module 目录调整。
设计约束
完成条件
- unit/contract tests 中无未经批准的固定 sleep;
- 所有 polling 有 deadline;
- 关键时间组件可注入 clock;
- 高风险包
-count=50 和 race 重复运行稳定;
- CI 有防新增 ratchet。
结论
仓库中多处并发、event bus、rate limit、WebSocket、adapter 与 lifecycle 测试依赖固定
time.Sleep或无 deadline 的 polling loop 来等待异步行为。典型示例:
hub-server/internal/service/eventbus_test.goSleep(50ms);for Running/Pending > 0 { Sleep(5ms) }没有总 deadline。固定 sleep 既可能太短导致 CI 偶发失败,也可能过长拖慢测试;无 deadline polling 在缺陷出现时可能永久挂住。它们还掩盖了生产组件缺少 Wait/Drain/Clock seam 的设计问题。
风险
目标设计
建立仓库级测试等待原则:
事件完成
使用 channel、WaitGroup、barrier、callback probe 或组件提供的
WaitIdle(ctx):时间行为
Polling
若必须 eventually:
生产 API
对异步组件增加必要且非 test-only hack 的 lifecycle/observability:
WaitIdle(ctx);Drain(ctx);Start(ctx)/Close();实施步骤
_test.go中time.Sleep、time.After、裸 polling;必须测试/验收
-count=50稳定;-count=10;time.Sleepfixture 会失败,合法 allowlist 可解释。推荐命令:
按实际 module 目录调整。
设计约束
runtime.Gosched()伪同步;完成条件
-count=50和 race 重复运行稳定;