【问题标题】:Android Instrumentation Testing - UI Thread IssuesAndroid Instrumentation 测试 - UI 线程问题
【发布时间】:2016-05-13 01:25:11
【问题描述】:

我正在尝试为我的 Android 应用编写仪器测试。

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

我的原始测试:

@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)上调用方法

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

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

非常感谢任何帮助或建议!

【问题讨论】:

    标签: java android multithreading testing junit


    【解决方案1】:

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

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

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

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

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

    通过这个运行,你的测试应该可以工作了。

    您收到的非法状态异常似乎是由于您与规则的交互。 documentation 状态

    请注意,如果存在此注释,则可能无法使用检测方法。

    如果您在@Before 开始/获取您的活动,它也应该可以工作。

    【讨论】:

    • runOnUiThread 方法与getInstrumentation().waitForIdleSync(); 结合使用时有效。不幸的是,@Before 方法不起作用。感谢您的帮助!
    • ^ 这在你有一个断言之后很重要,否则你会得到一个测试结果不一致的竞争条件!
    【解决方案2】:

    您可以在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))不同。

    【讨论】:

      【解决方案3】:

      已接受的答案现已弃用

      最简单的方法就是使用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
              }
      

      【讨论】:

      • 如果未来的访问者偶然发现Android.Test.UIThreadTest is deprecated as of API 24: Android 7.0 (Nougat),请注意这不是同一个注释。这个答案是使用支持注释。这对我来说无法解决
      • This answer 在牛轧糖上为我工作
      • myTest 中的activity 是从哪里来的?这就是我所坚持的。
      【解决方案4】:

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

              UiThreadStatement.runOnUiThread {
                 // call activity here 
              }
      

      【讨论】:

        【解决方案5】:

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

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

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

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

        mainThreadExecutor 本身带有 @MainThread 注释。

        换句话说,Espresso 也需要遵循 David 在接受的答案中描述的相同规则。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多