被测代码在onCreate()中运行时,如何使用Idling Resource?
问题描述
我正在学习在Espresso测试中使用Idling Resource,但遇到了一个棘手的问题:待测试的代码在Activity的onCreate()方法里就直接执行了。之前看到有人建议在@Before注解的方法里注册Idling Resource,但这根本行不通——因为注册需要拿到Activity实例,可启动Activity时onCreate()(甚至onStart()、onResume())已经跑完了,此时Idling Resource还没注册上:
private ActivityScenario<MyActivity> scenario; private IdlingResource myIdlingResource; @Before public void setUp() { scenario = ActivityScenario.launch(MyActivity.class); // 这里已经执行完onCreate等生命周期方法,Idling Resource还是null scenario.onActivity(activity -> { // 现在拿到Activity实例,但已经晚了;这个方法没法在Activity启动前调用 myIdlingResource= activity.getIdlingResource(); IdlingRegistry.getInstance().register(myIdlingResource); }); } @Test public void test() { // 断言Activity创建时发生的操作 }
这种情况下该怎么正确使用Idling Resource?
解决方案
方案1:让Activity在测试模式下等待Idling Resource注册
修改你的MyActivity,加入测试模式判断,延迟执行onCreate里的耗时任务,直到Idling Resource注册完成:
- 在Activity中添加测试用的同步锁:
public class MyActivity extends AppCompatActivity { // 仅用于测试的同步锁 @VisibleForTesting public static CountDownLatch testLatch; private MyIdlingResource idlingResource; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my); idlingResource = new MyIdlingResource(); // 如果是测试模式,先等待信号再执行耗时任务 if (testLatch != null) { try { testLatch.await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } // 原来的耗时任务逻辑 startLongRunningTask(); } @VisibleForTesting public IdlingResource getIdlingResource() { return idlingResource; } private void startLongRunningTask() { // 耗时操作开始时标记非空闲 idlingResource.setIdleState(false); // 你的耗时逻辑(比如网络请求、异步任务) // ... // 耗时操作结束时标记空闲 idlingResource.setIdleState(true); } }
- 在测试类中先初始化锁,启动Activity后注册Idling Resource再释放锁:
private ActivityScenario<MyActivity> scenario; private IdlingResource myIdlingResource; @Before public void setUp() { // 初始化锁,让Activity的onCreate暂时阻塞 MyActivity.testLatch = new CountDownLatch(1); // 启动Activity,此时onCreate会卡在await()处 scenario = ActivityScenario.launch(MyActivity.class); // 拿到Activity实例,注册Idling Resource后释放锁 scenario.onActivity(activity -> { myIdlingResource = activity.getIdlingResource(); IdlingRegistry.getInstance().register(myIdlingResource); MyActivity.testLatch.countDown(); }); } @After public void tearDown() { if (myIdlingResource != null) { IdlingRegistry.getInstance().unregister(myIdlingResource); } // 重置锁,避免影响其他测试用例 MyActivity.testLatch = null; } @Test public void testActivityCreation() { // Espresso会自动等待Idling Resource变为空闲,此时可以安全断言 onView(withId(R.id.result_text)).check(matches(withText("预期结果"))); }
方案2:使用全局静态Idling Resource
把Idling Resource做成全局可访问的实例,在测试启动前就注册,让Activity直接引用这个实例:
- 创建全局Idling Resource类:
public class GlobalIdlingResource implements IdlingResource { private static GlobalIdlingResource instance; private ResourceCallback callback; private boolean isIdle = true; public static GlobalIdlingResource getInstance() { if (instance == null) { instance = new GlobalIdlingResource(); } return instance; } @Override public String getName() { return GlobalIdlingResource.class.getName(); } @Override public boolean isIdleNow() { return isIdle; } @Override public void registerIdleTransitionCallback(ResourceCallback callback) { this.callback = callback; } public void setIdleState(boolean isIdle) { this.isIdle = isIdle; if (isIdle && callback != null) { callback.onTransitionToIdle(); } } }
- 在Activity的
onCreate中直接使用全局实例:
public class MyActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my); GlobalIdlingResource idlingResource = GlobalIdlingResource.getInstance(); idlingResource.setIdleState(false); // 模拟耗时任务 new Handler(Looper.getMainLooper()).postDelayed(() -> { // 任务完成,标记为空闲 idlingResource.setIdleState(true); // 更新UI等操作 }, 3000); } }
- 在测试类中先注册全局Idling Resource,再启动Activity:
private IdlingResource globalIdlingResource; @Before public void setUp() { // 先注册全局Idling Resource globalIdlingResource = GlobalIdlingResource.getInstance(); IdlingRegistry.getInstance().register(globalIdlingResource); // 再启动Activity,此时onCreate里的任务会关联到已注册的Idling Resource ActivityScenario.launch(MyActivity.class); } @After public void tearDown() { if (globalIdlingResource != null) { IdlingRegistry.getInstance().unregister(globalIdlingResource); } } @Test public void testActivityCreation() { onView(withId(R.id.result_text)).check(matches(withText("预期结果"))); }
方案3:替换线程池为IdlingThreadPoolExecutor
如果你的耗时任务是通过线程池执行的,可以直接用Espresso提供的IdlingThreadPoolExecutor替换原线程池,无需修改Activity核心代码:
- 在测试类中创建并注册Idling线程池:
private IdlingThreadPoolExecutor idlingExecutor; @Before public void setUp() { // 创建带Idling能力的线程池 ExecutorService delegate = Executors.newSingleThreadExecutor(); idlingExecutor = new IdlingThreadPoolExecutor("TestExecutor", delegate); IdlingRegistry.getInstance().register(idlingExecutor); // 把线程池注入到Activity(可以用依赖注入,或者静态方法替换) MyActivity.setExecutor(idlingExecutor); // 启动Activity ActivityScenario.launch(MyActivity.class); } @After public void tearDown() { if (idlingExecutor != null) { IdlingRegistry.getInstance().unregister(idlingExecutor); idlingExecutor.shutdown(); } }
- 在Activity中使用注入的线程池执行任务:
public class MyActivity extends AppCompatActivity { private static ExecutorService executor = Executors.newSingleThreadExecutor(); @VisibleForTesting public static void setExecutor(ExecutorService executor) { MyActivity.executor = executor; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my); executor.execute(() -> { // 耗时操作逻辑 runOnUiThread(() -> { // 更新UI }); }); } }
内容的提问来源于stack exchange,提问作者flauschtrud
相关产品推荐
相关产品推荐

