Android Instrumentation测试 - UI线程问题

时间:2016-02-03 17:41:35

标签: java android multithreading testing junit

我正在为我的Android应用程序编写一个Instrumentation 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")));
    }
}

这导致

  

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

     

...

     

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

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

经过研究后,似乎在我的测试中添加UiThreadTestRuleandroid.support.test.annotation.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:无法在主应用程序线程上调用方法(on:main)

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

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

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

5 个答案:

答案 0 :(得分:23)

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

您必须将您的工具视为与实际应用一起安装的内容,因此您可能的互动是“有限的”。

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

你可以运行你需要在主线程上测试的代码,就像你使用

从另一个线程在你的app中调用它一样
activity.runOnUiThread(new Runnable() {
    public void run() {
        activity.updateDetails(workOrder);
    }
}

运行此测试应该可以正常运行。

您收到的非法州例外似乎是因为您与规则的互动。 documentation

  

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

如果您在@Before开始/接受活动,它也应该有用。

答案 1 :(得分:16)

您可以在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")));
}

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

FYR,以下是UiThreadTest的Javadoc引用:

  

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

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

答案 2 :(得分:12)

现已接受的答案已弃用

实现此目标的最简单方法是使用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
        }

答案 3 :(得分:1)

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

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

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

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

mainThreadExecutor本身已注明@MainThread

换句话说,Espresso也需要按照David所接受的答案采用相同的规则。

答案 4 :(得分:0)

借助androidx测试运行程序,添加了一个新类UiThreadStatement,该类为此提供了runOnUiThread方法。

        UiThreadStatement.runOnUiThread {
           // call activity here 
        }