Тестирование инструментальных средств Android - проблемы с интерфейсом пользовательского интерфейса

Я пытаюсь написать Test Instrumentation Test для своего приложения для 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().

После небольшого исследования казалось, что добавление аннота UiThreadTestRule и android.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: метод нельзя вызвать в главном потоке приложения (на: main)

(Примечание. Все методы в этой трассировке стека не являются моим кодом)

Кажется, это дает мне смешанные результаты... Если его нужно запустить в исходном потоке, который создал представления, но не может работать в основном потоке, в каком потоке он должен быть запущен?

Я бы очень признателен за любую помощь или предложения!

Ответы

Ответ 1

Те контрольные тесты запускаются внутри собственного приложения. Это также означает, что они запускаются в своем потоке.

Вы должны думать о своей аппаратуре как о том, что вы устанавливаете рядом с вашим реальным приложением, поэтому ваши возможные взаимодействия "ограничены".

Вам нужно вызвать все методы просмотра из UIThread/основного потока приложения, поэтому вызов activity.updateDetails(workOrder); из вашей инструментальной нити не является основным потоком приложения. Вот почему выбрано исключение.

Вы можете просто запустить код, который вам нужно протестировать в своем основном потоке, как если бы вы его вызывали в своем приложении из другого потока, используя

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

С этим запуском ваш тест должен работать.

Незаконное государственное исключение, которое вы получаете, похоже, связано с вашим взаимодействием с правилом. документация утверждает

Обратите внимание, что при использовании этой аннотации методы инструментария не могут использоваться.

Если вы начинаете/получаете свою деятельность в @Before, она также должна работать.

Ответ 2

Вы можете выполнить часть своего теста в основном потоке пользовательского интерфейса с помощью 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")));
}

В большинстве случаев проще аннотировать тестовый метод с помощью UiThreadTest, однако он может нести другие ошибки, такие как java.lang.IllegalStateException: Method cannot be called on the main application thread (on: main).

FYR, вот цитата из UiThreadTest Javadoc:

Обратите внимание, что из-за текущего ограничения JUnit методы, аннотированные с помощью Before и After, также будут выполняться в потоке пользовательского интерфейса. Рассмотрите возможность использования runOnUiThread (Runnable), если это проблема.

Обратите внимание, что упомянутый выше UiThreadTest (package android.support.test.annotation) отличается от (UiThreadTest (package android.test)).

Ответ 3

Принятый ответ устарел. Правильный способ сделать это сейчас просто:

@Test
@UiThreadTest
public void myTest() {
    // ...
}

Ответ 4

В принятом ответе описывается, что происходит отлично.

В качестве дополнения, если кому-то интересно, почему методы Espresso, которые касаются пользовательского интерфейса, например. perform(ViewActions ...) не нужно делать то же самое, просто потому, что они в конечном итоге делают это позже для нас.

Если вы следуете за perform(ViewActions ...), вы обнаружите, что он заканчивает выполнение следующего (в android.support.test.espresso.ViewInteraction):

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

Это mainThreadExecutor само помечается с помощью @MainThread.

Другими словами, Espresso также должен играть по тем же правилам, которые описаны Дэвидом в принятом ответе.