Android Espresso测试遇AppNotIdleException问题求助
我来帮你捋捋这个问题——你遇到的AppNotIdleException本质上是Espresso在等待应用进入空闲状态时超时了。当你的TextView通过后台进程加载内容后,大概率是你的后台任务还在持续运行,或者界面有未告知Espresso的持续更新操作,导致它一直认为应用没“喘口气”的机会,所以拒绝执行后续的点击操作。
先说说你测试代码里的一个明显问题:你用了好多Thread.sleep(),这在Espresso测试里是大忌。Espresso本身就是基于“等待应用空闲”的机制来执行操作的,硬编码的sleep不仅会让测试不稳定(不同设备性能不同,sleep时间要么不够要么太长),还会掩盖真正的问题根源。
接下来给你几个具体的解决方案,按优先级来:
1. 用IdlingResource让Espresso感知后台任务
Espresso默认只会等待主线程空闲,但如果你的后台任务是在其他线程(比如AsyncTask、Retrofit请求、Room数据库操作),它是不知道的。这时候需要用IdlingResource来告诉Espresso:“我有个后台任务在跑,等它结束你再继续执行测试”。
具体步骤:
第一步:创建全局的CountingIdlingResource
在你的应用代码里(不是测试代码),新建一个类来管理空闲资源:
public class AppIdlingResource { private static final String RESOURCE_NAME = "APP_GLOBAL_IDLE"; private static final CountingIdlingResource countingIdlingResource = new CountingIdlingResource(RESOURCE_NAME); // 后台任务开始时调用 public static void increment() { countingIdlingResource.increment(); } // 后台任务结束(包括UI更新完成)时调用 public static void decrement() { if (!countingIdlingResource.isIdleNow()) { countingIdlingResource.decrement(); } } // 给测试代码提供注册用的资源实例 public static IdlingResource getIdlingResource() { return countingIdlingResource; } }
第二步:在后台任务中标记状态
找到你加载TextView内容的后台任务(比如网络请求、数据库查询),在任务开始前调用AppIdlingResource.increment(),在任务完成(包括更新完TextView的内容后)调用AppIdlingResource.decrement()。
比如用Retrofit的例子:
// 开始请求前标记 AppIdlingResource.increment(); apiService.getSpo2Data() .enqueue(new Callback<Spo2Data>() { @Override public void onResponse(Call<Spo2Data> call, Response<Spo2Data> response) { // 更新TextView内容 txtSPO2Value.setText(response.getData().getValue()); // 请求完成后标记 AppIdlingResource.decrement(); } @Override public void onFailure(Call<Spo2Data> call, Throwable t) { // 失败也要标记,避免Espresso一直等待 AppIdlingResource.decrement(); } });
第三步:在测试代码中注册IdlingResource
修改你的测试类,在测试前后注册/注销这个资源:
@Rule public ActivityTestRule<MainActivity> activityRule = new ActivityTestRule<>(MainActivity.class); @Before public void setup() { // 注册空闲资源 Espresso.registerIdlingResources(AppIdlingResource.getIdlingResource()); } @After public void teardown() { // 注销空闲资源 Espresso.unregisterIdlingResources(AppIdlingResource.getIdlingResource()); }
2. 优化测试代码,移除Thread.sleep()
现在可以把测试里的所有Thread.sleep()删掉,改用Espresso的内置等待机制,它会自动等到应用空闲再执行操作:
@Test @MediumTest public void Test1_Spo2() { PatientToMainActivity(); // 等待ViewPager可见 onView(withId(R.id.viewPager)) .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))); // 验证txtSPO2显示正确 onView(allOf(withId(R.id.txtSPO2), withText("SpO2"))) .check(matches(isCompletelyDisplayed())); // 等待txtSPO2Value加载内容并可点击,然后执行点击 onView(withId(R.id.txtSPO2Value)) .check(matches(isDisplayed())) .check(matches(not(withText("")))) // 确保内容已加载 .check(matches(isClickable())) .perform(click()); // 验证跳转是否正确 intended(hasComponent(new ComponentName(getTargetContext(), ChartActivity.class))); }
3. 检查是否有持续的UI更新
如果上面的方法还没解决问题,你要检查TextView加载内容后,是否有持续的UI操作在运行:比如动画、轮询定时器、实时刷新的任务。这些操作会让Espresso一直认为应用处于忙碌状态。
解决办法:
- 如果是测试不需要的动画,可以在测试时禁用:在测试类里添加
@Rule来禁用动画:
(这个Rule需要依赖@Rule public DisableAnimationsRule disableAnimationsRule = new DisableAnimationsRule();androidx.test:rules库) - 如果是轮询任务,可以在测试模式下暂停,或者给它也注册对应的IdlingResource。
最后提醒
永远不要依赖Thread.sleep()来等待UI更新,Espresso的设计就是为了避免这种不稳定的写法。只要正确使用IdlingResource,它会精准地等到应用空闲时再执行下一步操作,测试的稳定性会大大提升。
内容的提问来源于stack exchange,提问作者Kavitha

