简体   繁体   English

Android Instrumentation 测试 - UI 线程问题

[英]Android Instrumentation Testing - UI Thread Issues

I am trying to write an Instrumentation Test for my Android app.我正在尝试为我的 Android 应用程序编写仪器测试。

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. android.view.ViewRootImpl$CalledFromWrongThreadException:只有创建视图层次结构的原始线程才能触摸其视图。

... ...

com.kwtree.kwtree.workorder.WorkOrderDetails.updateDetails(WorkOrderDetails.java:155) 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 UiThreadTestRule and android.support.test.annotation.UiThreadTest annotation 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!我真的很感激任何帮助或建议!

Those instrumentation tests run inside their own app.这些仪器测试在他们自己的应用程序中运行。 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);您需要从应用程序的 UIThread/主线程调用所有视图方法,因此调用activity.updateDetails(workOrder); from your instrumentation thread is not the application main thread.来自您的检测线程不是应用程序主线程。 This is why the exception is thrown.这就是抛出异常的原因。

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 documentation states文件指出

Note that instrumentation methods may not be used when this annotation is present.请注意,存在此注释时,可能无法使用检测方法。

If you start / get your activity in @Before it should also work.如果您在@Before开始/获取您的活动,它也应该有效。

You can run portion of your test on the main UI thread with the help of UiThreadTestRule.runOnUiThread(Runnable) :您可以在UiThreadTestRule.runOnUiThread(Runnable)的帮助下在主 UI 线程上运行部分测试:

@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) 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的 Javadoc:

Note, due to current JUnit limitation, methods annotated with Before and After will also be executed on the UI Thread.注意,由于当前的 JUnit 限制,使用BeforeAfter注释的方法也将在 UI 线程上执行。 Consider using runOnUiThread(Runnable) if this is an issue.如果这是一个问题,请考虑使用 runOnUiThread(Runnable)。

Please note UiThreadTest (package android.support.test.annotation ) mentioned above is different from ( UiThreadTest (package android.test )).请注意上面提到的UiThreadTest (package android.support.test.annotation ) 不同于 ( UiThreadTest (package android.test ))。

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
        }

With the androidx test runner a new class was added UiThreadStatement that gives a runOnUiThread method for this.使用 androidx 测试运行器添加了一个新类UiThreadStatement ,它runOnUiThread提供了一个runOnUiThread方法。

        UiThreadStatement.runOnUiThread {
           // call activity here 
        }

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 eg 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 mainThreadExecutor is 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 在接受的答案中描述的相同规则进行操作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM