java Android Instrumentation 测试 - UI 线程问题

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/35183981/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 23:50:25  来源:igfitidea点击:

Android Instrumentation Testing - UI Thread Issues

javaandroidmultithreadingtestingjunit

提问by Khalos

I am trying to write an Instrumentation Test for my Android app.

我正在尝试为我的 Android 应用程序编写一个 Instrumentation 测试。

I'm running into some weird threading issues and I can't seem to find a solution.

我遇到了一些奇怪的线程问题,但似乎找不到解决方案。

My Original Test:

我的原始测试:

@RunWith(AndroidJUnit4.class)
public class WorkOrderDetailsTest {

    @Rule
    public ActivityTestRule<WorkOrderDetails> activityRule = new ActivityTestRule<>(WorkOrderDetails.class);

    @Test
    public void loadWorkOrder_displaysCorrectly() throws Exception {
        final WorkOrderDetails activity = activityRule.getActivity();

        WorkOrder workOrder = new WorkOrder();
        activity.updateDetails(workOrder);

        //Verify customer info is displayed
        onView(withId(R.id.customer_name))
                .check(matches(withText("John Smith")));
    }
}

This resulted in an

这导致了

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

...

com.kwtree.kwtree.workorder.WorkOrderDetails.updateDetails(WorkOrderDetails.java:155)

android.view.ViewRootImpl$CalledFromWrongThreadException:只有创建视图层次结构的原始线程才能触摸其视图。

...

com.kwtree.kwtree.workorder.WorkOrderDetails.updateDetails(WorkOrderDetails.java:155)

The only thing the updateDetails()method does is some setText()calls.

updateDetails()方法唯一能做的就是一些setText()调用。

After researching a bit, it seemed like adding a UiThreadTestRuleand android.support.test.annotation.UiThreadTestannotation to my test would fix the problem.

经过一番研究,似乎在我的测试中添加一个UiThreadTestRuleandroid.support.test.annotation.UiThreadTest注释可以解决问题。

@UiThreadTest:

@UiThreadTest:

@RunWith(AndroidJUnit4.class)
public class WorkOrderDetailsTest {

    //Note: This is new
    @Rule
    public UiThreadTestRule uiThreadTestRule = new UiThreadTestRule();

    @Rule
    public ActivityTestRule<WorkOrderDetails> activityRule = new ActivityTestRule<>(WorkOrderDetails.class);

    @Test
    @UiThreadTest //Note: This is new
    public void loadWorkOrder_displaysCorrectly() throws Exception {
        final WorkOrderDetails activity = activityRule.getActivity();

        WorkOrder workOrder = new WorkOrder();
        activity.updateDetails(workOrder);

        //Verify customer info is displayed
        onView(withId(R.id.customer_name))
                .check(matches(withText("John Smith")));
    }
}

java.lang.IllegalStateException: Method cannot be called on the main application thread (on: main)

java.lang.IllegalStateException: 无法在主应用程序线程上调用方法 (on: main)

(Note: All of the methods in this stack trace are not my code)

(注意:此堆栈跟踪中的所有方法都不是我的代码)

It seems to be giving me mixed results... If it needs to be run on the original thread that created the views but can't run on the main thread, what thread should it be run on?

它似乎给了我混合的结果......如果它需要在创建视图的原始线程上运行但不能在主线程上运行,它应该在哪个线程上运行?

I'd really appreciate any help or suggestions!

我真的很感激任何帮助或建议!

采纳答案by David Medenjak

Those instrumentation tests run inside their ownapp. This also means, they run in their own thread.

这些仪器测试在他们自己的应用程序中运行。这也意味着,它们在自己的线程中运行。

You must think of your instrumentation as something you install alongside your actual app, so your possible interactions are 'limited'.

您必须将仪器视为与实际应用程序一起安装的东西,因此您可能的交互是“有限的”。

You need to call all view methods from the UIThread / main thread of the application, so calling activity.updateDetails(workOrder);from your instrumentation thread is notthe application main thread. This is why the exception is thrown.

您需要从应用程序的 UIThread/主线程调用所有视图方法,因此activity.updateDetails(workOrder);从您的检测线程调用不是应用程序主线程。这就是抛出异常的原因。

You can just run the code you need to test on your main thread like you would do if you were calling it inside your app from a different thread by using

您可以在主线程上运行您需要测试的代码,就像您在应用程序中从不同的线程调用它一样,使用

activity.runOnUiThread(new Runnable() {
    public void run() {
        activity.updateDetails(workOrder);
    }
}

With this running your test should work.

这样运行您的测试应该可以工作。

The illegal state exception you are receiving seems to be because of your interaction with the rule. The documentationstates

您收到的非法状态异常似乎是因为您与规则的交互。该文件指出

Note that instrumentation methods may not be used when this annotation is present.

请注意,当存在此注释时,可能无法使用检测方法。

If you start / get your activity in @Beforeit should also work.

如果你开始/开始你的活动,@Before它也应该有效。

回答by Jeremy Kao

You can run portion of your test on the main UI thread with the help of UiThreadTestRule.runOnUiThread(Runnable):

您可以借助以下工具在主 UI 线程上运行部分测试UiThreadTestRule.runOnUiThread(Runnable)

@Rule
public UiThreadTestRule uiThreadTestRule = new UiThreadTestRule();

@Test
public void loadWorkOrder_displaysCorrectly() throws Exception {
    final WorkOrderDetails activity = activityRule.getActivity();

    uiThreadTestRule.runOnUiThread(new Runnable() {
        @Override
        public void run() {
            WorkOrder workOrder = new WorkOrder();
            activity.updateDetails(workOrder);
        }
    });

    //Verify customer info is displayed
    onView(withId(R.id.customer_name))
            .check(matches(withText("John Smith")));
}

In most cases it is simpler to annotate the test method with UiThreadTest, however, it may incur other errors such as java.lang.IllegalStateException: Method cannot be called on the main application thread (on: main).

在大多数情况下,使用 注释测试方法更简单UiThreadTest,但是,它可能会导致其他错误,例如java.lang.IllegalStateException: Method cannot be called on the main application thread (on: main)

FYR, here is a quote from UiThreadTest's Javadoc:

FYR,这里引用自UiThreadTest's Javadoc:

Note, due to current JUnit limitation, methods annotated with Beforeand Afterwill also be executed on the UI Thread. Consider using runOnUiThread(Runnable) if this is an issue.

请注意,由于当前的 JUnit 限制,使用Before和注释的方法After也将在 UI 线程上执行。如果这是一个问题,请考虑使用 runOnUiThread(Runnable)。

Please note UiThreadTest(package android.support.test.annotation) mentioned above is different from (UiThreadTest(package android.test)).

请注意上面提到的UiThreadTest(包android.support.test.annotation)与(UiThreadTest(包android.test))不同。

回答by Greg Ennis

The accepted answer is now deprecated

已接受的答案现已弃用

The easiest way to achieve this is simply using UiThreadTest

实现这一目标的最简单方法是简单地使用 UiThreadTest

import android.support.test.annotation.UiThreadTest;

        @Test
        @UiThreadTest
        public void myTest() {
            // Set up conditions for test

            // Call the tested method
            activity.doSomethingWithAView()

            // Verify that the results are correct
        }

回答by harmanjd

With the androidx test runner a new class was added UiThreadStatementthat gives a runOnUiThreadmethod for this.

使用 androidx 测试运行器添加了一个新类,该类UiThreadStatement提供了一种runOnUiThread方法。

        UiThreadStatement.runOnUiThread {
           // call activity here 
        }

回答by joakim

The accepted answer describes what is going on perfectly.

接受的答案完美地描述了正在发生的事情。

As an addition, in case someone is curious why Espresso's methods that touch the UI e.g. perform(ViewActions ...)don't need to do the same, it is simply because they end up doing it later for us.

另外,如果有人好奇为什么 Espresso 的方法接触 UI,例如perform(ViewActions ...)不需要做同样的事情,那只是因为他们最终会为我们做这件事。

If you follow perform(ViewActions ...)you will find it ends up doing the following (in android.support.test.espresso.ViewInteraction):

如果您遵循,perform(ViewActions ...)您会发现它最终会执行以下操作(在 中android.support.test.espresso.ViewInteraction):

private void runSynchronouslyOnUiThread(Runnable action) {
    ...
    mainThreadExecutor.execute(uiTask);
    ...
}

That mainThreadExecutoris itself annotated with @MainThread.

mainThreadExecutor本身是用@MainThread.

In other words, Espresso also needs to play by the same rules described by David on the accepted answer.

换句话说,Espresso 也需要按照 David 在已接受答案中描述的相同规则进行操作。