Espresso中如何等待异步事件?及异步操作后重试检查字符串问题
嘿,我来帮你理清这几个测试中常见的Espresso问题,咱们一个个拆解:
一、正确等待异步事件的方式
Espresso本身会自动等待主线程空闲,但对于自定义的异步操作(比如网络请求、后台线程任务),它没法自动感知,这时候就得用IdlingResource来告诉Espresso什么时候操作完成——这是官方推荐的方式,比自己写循环或sleep靠谱多了。
1. 使用CountingIdlingResource(最常用)
这是一个计数型的IdlingResource,适合跟踪多个异步任务:
// 全局定义或在测试类中声明 private val asyncIdlingResource = CountingIdlingResource("ASYNC_TASK") // 在你的异步操作开始前调用increment() asyncIdlingResource.increment() // 在异步操作的回调/完成方法中调用decrement() yourAsyncOperation { result -> // 处理结果 asyncIdlingResource.decrement() } // 在测试类中注册/注销IdlingResource @Before fun setup() { IdlingRegistry.getInstance().register(asyncIdlingResource) } @After fun teardown() { IdlingRegistry.getInstance().unregister(asyncIdlingResource) }
注册后,Espresso会自动等待asyncIdlingResource的计数归0(也就是异步操作完成),再执行后续的检查逻辑,完全不需要手动等待。
2. 避免使用Thread.sleep()
绝对不要用Thread.sleep()来等待异步操作——它不管操作实际是否完成,纯粹靠固定时间等待,很容易导致测试不稳定(比如网络慢的时候超时,网络快的时候浪费时间)。
二、Espresso处理失败的不一致性原因
你遇到的“不一致”,本质是不同检查操作抛出的异常类型不一样:
- 当检查视图是否存在时(比如
onView(withId(R.id.btn)).check(matches(exists()))),如果视图找不到,会抛出NoMatchingViewException。 - 当检查视图文本是否匹配时(比如
onView(withId(R.id.text)).check(matches(withText("expected")))):- 如果视图本身不存在,还是会抛出
NoMatchingViewException; - 如果视图存在但文本不匹配,会抛出
AssertionFailedError。
- 如果视图本身不存在,还是会抛出
你之前的try-catch只捕获了NoMatchingViewException,但文本不匹配时抛出的是另一种异常,导致重试逻辑没触发,看起来就像“不抛出异常”——其实是你没覆盖到对应的异常类型。
三、实现字符串显示的重试检查方案
方案1:优先用IdlingResource(最可靠)
如果能改造异步操作,用IdlingResource标记完成状态,那根本不需要写重试逻辑,直接写正常的检查即可:
// 点击按钮触发异步操作 onView(withId(R.id.trigger_btn)).perform(click()) // Espresso会自动等待异步完成,再检查文本 onView(withId(R.id.result_text)).check(matches(withText("操作成功")))
方案2:自定义重试辅助方法(适合无法用IdlingResource的场景)
如果因为某些限制没法用IdlingResource,可以写一个通用的重试方法,捕获所有可能的失败异常,循环重试直到超时:
/** * 重试执行Espresso检查操作 * @param maxAttempts 最大重试次数 * @param delayMs 每次重试间隔时间 * @param action 要执行的检查操作 */ fun retryEspressoCheck(maxAttempts: Int = 5, delayMs: Long = 1000, action: () -> Unit) { var attempts = 0 var isSuccess = false while (attempts < maxAttempts && !isSuccess) { try { action() isSuccess = true } catch (e: NoMatchingViewException) { // 视图找不到,重试 attempts++ Thread.sleep(delayMs) } catch (e: AssertionFailedError) { // 断言失败(比如文本不匹配),重试 attempts++ Thread.sleep(delayMs) } } if (!isSuccess) { throw AssertionError("检查失败,已重试$maxAttempts次") } }
然后在测试中这样用:
onView(withId(R.id.trigger_btn)).perform(click()) // 重试检查文本是否显示 retryEspressoCheck { onView(withId(R.id.result_text)).check(matches(withText("操作成功"))) }
注意:Thread.sleep()在这里是妥协方案,尽量还是用IdlingResource,因为sleep会增加测试时长,且稳定性不如官方的等待机制。
内容的提问来源于stack exchange,提问作者Samuel

