Espresso 测试被后台线程阻止。应用程序不空闲异常:“AppNotIdleException。”

问题描述 投票:0回答:4

我的 android espresso 单元测试由于某些后台线程不空闲而被阻止。我如何找出哪个线程正在阻止我的应用程序执行?

        android.support.test.espresso.AppNotIdleException: Looped for 246 iterations over 60 SECONDS. The following Idle Conditions failed ASYNC_TASKS_HAVE_IDLED.
        at dalvik.system.VMStack.getThreadStackTrace(Native Method)
        at java.lang.Thread.getStackTrace(Thread.java:580)
        at android.support.test.espresso.base.DefaultFailureHandler.getUserFriendlyError(DefaultFailureHandler.java:92)
        at android.support.test.espresso.base.DefaultFailureHandler.handle(DefaultFailureHandler.java:56)
        at android.support.test.espresso.ViewInteraction.runSynchronouslyOnUiThread(ViewInteraction.java:184)
        at android.support.test.espresso.ViewInteraction.doPerform(ViewInteraction.java:115)
        at android.support.test.espresso.ViewInteraction.perform(ViewInteraction.java:87)
        at android.support.test.espresso.Espresso.closeSoftKeyboard(Espresso.java:159)
android multithreading android-asynctask android-espresso
4个回答
4
投票

作为开始:

Espresso for Android 是完美且快速的测试自动化框架, 但它有一个重要的限制——你只能操作 在测试上下文下的应用程序内。

来自: http://qathread.blogspot.com/2015/05/espresso-uiautomator-perfect-tandem.html

这意味着

Espresso
执行任何操作都需要在应用程序的主线程上操作。它检查 UI 线程何时处于
idle()
,如果不是,则等待直到 UI 线程再次空闲。

如果 UI Thread 空闲时间不是太长,就会产生

Espressso IdlingResources
错误,如
AppNotIdleException
,这意味着:

异常,表明App在指定的时间后仍未空闲。

要解决这个问题,您需要创建自己的

IdlingResource
方法来表示
Espresso
,而 UI 线程则为
idle()

让我们更深入地了解一下问题:

Espresso 引入了

IdlingResource
的概念,这是一个简单的 界面:

代表被测应用程序的资源 这可能会导致测试期间发生异步后台工作 执行

接口定义了三个方法:

  • getName():必须返回一个非空字符串,用于标识空闲资源。
  • isIdleNow():返回空闲资源当前的空闲状态。如果返回true,则
    上的onTransitionToIdle()方法 注册的 ResourceCallback 必须之前已调用过。
  • registerIdleTransitionCallback(IdlingResource.ResourceCallbackcallback):通常该方法用于存储对
    的引用 回调以通知其空闲状态发生变化。

例如,等待空闲资源的实现 在 WebView 中完全加载的页面将如下所示:

public class WebViewIdlingResource extends WebChromeClient implements IdlingResource {

    private static final int FINISHED = 100;

    private WebView webView;
    private ResourceCallback callback;

    private WebViewIdlingResource(WebView webView) {
        this.webView = checkNotNull(webView,
                String.format("Trying to instantiate a \'%s\' with a null WebView", getName())));
        // Shall we save the original client? Atm it's not used though.
        this.webView.setWebChromeClient(this);
    }

    @Override public void onProgressChanged(WebView view, int newProgress) {
        if (newProgress == FINISHED && view.getTitle() != null && callback != null) {
            callback.onTransitionToIdle();
        }
    }

    @Override public void onReceivedTitle(WebView view, String title) {
        if (webView.getProgress() == FINISHED && callback != null) {
            callback.onTransitionToIdle();
        }
    }

    @Override public String getName() {
        return "WebView idling resource";
    }

    @Override public boolean isIdleNow() {
        // The webView hasn't been injected yet, so we're idling
        if (webView == null) return true;
        return webView.getProgress() == FINISHED && webView.getTitle() != null;
    }

    @Override public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
        this.callback = resourceCallback;
    }
}

创建自己的自定义闲置资源后,需要将其 通过致电注册 Espresso

Espresso.registerIdlingResource(webViewIdlingResource)

来自:http://dev.jimdo.com/2014/05/09/wait-for-it-a-deep-dive-into-espresso-s-idling-resources/

如果它不起作用,请尝试与

Espresso
一起使用另一个名为[
uiatomator
的Google测试框架,这可能会帮助你解决这个问题。

希望对你有帮助


1
投票

我也有同样的问题。我编写了一个 DumThreadRule 来转储线程,以防测试失败。如果有人需要的话,这是代码:

class DumpThreadRule() : TestWatcher() {
    private fun dumpThreads() {
        val threads = Thread.getAllStackTraces()
        threads.forEach {
            System.err.println(it.key.getName() + ": " + it.key.getState())
            for (stackTraceElement in it.key.getStackTrace()) {
                System.err.println("\t" + stackTraceElement)
            }
        }
    }

    override fun failed(e: Throwable?, description: Description?) {
        super.failed(e, description)
        dumpThreads()
    }
}

您可以在测试中使用:

@Rule
@JvmField
var dumpThreadRule = DumpThreadRule()

但是,就我而言,所有线程要么是“RUNNABLE”,“TIMED_WAITING”要么是“WAITING”。他们都没有“运行”。因此我的 UI 线程应该是“IDLE”不是?


0
投票

您可以创建线程转储来查看是什么阻止了您的应用程序。有时,当您创建多个线程转储时它会有所帮助。


0
投票

我在将大量 UI 测试转移到 robolectric 后看到了这一点。 但我并不总是看到它。 我相当确定在我的情况下发生了什么,是某些东西正在泄漏内存,过了一会儿,JVM 陷入了困境,然后事情发生得非常缓慢,然后就超出了超时时间。 如果我使用非常低的值(例如 10)调用 setForkEvery,则问题永远不会发生。 如果我不全部设置,我将无法在没有 OutMemoryException 的情况下运行所有测试。 值在 50 到 100 之间时,要么成功,要么失败。 我的任务是找出导致泄漏/速度变慢的原因。 当不经常重新启动 JVM 时,测试运行得更快,但我希望测试能够完成。 我怀疑参数化测试是问题的根源。

© www.soinside.com 2019 - 2024. All rights reserved.