如何正确测试依赖Guava AsyncEventBus操作完成的代码?
测试依赖Guava AsyncEventBus操作完成的正确姿势
兄弟,我太懂你用Thread.sleep()搞AsyncEventBus测试的痛苦了——要么睡短了断言失败,睡长了测试慢得离谱,CI上还时不时抽风。分享几个亲测靠谱的解决办法,帮你把测试稳下来:
方案一:用CountDownLatch精准同步
这是最常用的异步测试同步手段,原理就是让测试线程等待事件处理器完成后再继续。
举个具体的例子:
// 1. 初始化一个倒计时门闩,计数设为你要等待的事件数量 CountDownLatch eventLatch = new CountDownLatch(1); // 2. 给EventBus注册一个临时的订阅者(或者修改现有订阅者逻辑) asyncEventBus.register(new Object() { @Subscribe public void handleTargetEvent(TargetEvent event) { // 这里可以保留原有业务逻辑,或者只做计数 eventLatch.countDown(); } }); // 3. 触发被测逻辑,让EventBus发布事件 testedObject.triggerEventSource(); // 4. 等待事件处理完成,设置合理超时避免死等 if (!eventLatch.await(3, TimeUnit.SECONDS)) { fail("事件处理超时,测试终止"); } // 5. 现在可以安全地对被测对象状态做断言了 assertThat(testedObject.getTargetState()).isEqualTo(expectedState);
这个方法的好处是精准等待,不用瞎猜sleep时长,超时还能快速失败,避免测试卡在那里浪费时间。
方案二:用同步Executor改造AsyncEventBus
如果你的测试场景允许事件同步执行(大部分单元测试都可以),直接给AsyncEventBus换个同步执行器就行——这样事件发布后会立刻在当前线程处理完,完全不需要等待逻辑。
代码示例:
// 创建一个同步执行器:直接在当前线程跑任务 Executor syncExecutor = Runnable::run; // 初始化测试用的同步AsyncEventBus AsyncEventBus syncEventBus = new AsyncEventBus(syncExecutor); // 把被测对象里的AsyncEventBus替换成这个测试实例(可以用依赖注入或者反射) testedObject.setEventBus(syncEventBus); // 触发逻辑后直接断言,因为事件已经同步处理完了 testedObject.triggerEventSource(); assertThat(testedObject.getTargetState()).isEqualTo(expectedState);
这个方案最简洁,测试代码几乎不用额外的等待逻辑,推荐优先使用。
方案三:自定义可跟踪的EventBus包装器
如果你的测试场景很复杂(比如要等待多个不同事件全部处理完成),可以封装一个跟踪型的EventBus,统一管理事件处理状态。
大致实现思路:
public class TrackableAsyncEventBus extends AsyncEventBus { private final List<CompletableFuture<Void>> taskFutures = Collections.synchronizedList(new ArrayList<>()); public TrackableAsyncEventBus(Executor executor) { super(wrapExecutor(executor)); } private static Executor wrapExecutor(Executor original) { return runnable -> { // 把每个事件处理任务包装成Future,记录下来 CompletableFuture<Void> future = CompletableFuture.runAsync(runnable, original); taskFutures.add(future); }; } // 提供等待所有任务完成的方法 public boolean awaitAllTasks(long timeout, TimeUnit unit) throws InterruptedException { try { CompletableFuture.allOf(taskFutures.toArray(new CompletableFuture[0])).get(timeout, unit); return true; } catch (TimeoutException | ExecutionException e) { return false; } finally { taskFutures.clear(); } } }
测试时用这个包装类,触发事件后调用awaitAllTasks()就能等待所有事件处理完成,适合复杂的多事件场景。
总的来说,绝对不要依赖Thread.sleep()——它完全依赖环境性能,是测试不稳定的根源。优先用同步Executor方案,复杂场景用CountDownLatch或自定义跟踪器,能让你的测试既稳定又高效。
内容的提问来源于stack exchange,提问作者P Marecki
相关产品推荐
相关产品推荐

